经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » HTML/CSS » HTML5 » 查看文章
Vue.js 源码分析(十三) 基础篇 组件 props属性详解
来源:cnblogs  作者:大沙漠  时间:2019/6/27 9:52:51  对本文有异议

父组件通过props属性向子组件传递数据,定义组件的时候可以定义一个props属性,值可以是一个字符串数组或一个对象。

例如:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Document</title>
  6. <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
  7. </head>
  8. <body>
  9. <div id="app"><child :title="message"></child></div>
  10. <script>
  11. Vue.component('child',{
  12. template:'<h1>{{title}}</h1>',props:['title']       //这里props是一个字符串数组
  13. })
  14. var app = new Vue({
  15. el:'#app',data:{message:'Hello World'}
  16. })
  17. </script>
  18. </body>
  19. </html>

这里我们给child这个组件定义了名为title的props,父组件通过title特性传递给子组件,渲染为:

props除了数组,也可以是一个对象,此时对象的键对应的props的名称,值又是一个对象,可以包含如下属性:

         type:        ;类型,可以设置为:String、Number、Boolean、Array、Object、Date等等             ;如果只设置type而未设置其他选项,则值可以直接用类型,例如:props:{title:Object}
        default      ;默认值
        required    ;布尔类型,表示是否必填项目
        validator    ;自定义验证函数   

例如:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
  6. <title>Document</title>
  7. </head>
  8. <body>
  9. <div id="app"><child></child></div>
  10. <script>
  11. Vue.component('child',{
  12. template:'<h1>{{title}}</h1>',props:{title:{default:'Hello World'}} //这里我们定义的title是个对象,含有默认值
  13. })
  14. var app = new Vue({
  15. el:'#app'
  16. })
  17. </script>
  18. </body>
  19. </html>

这里父组件app没有给子组件child传递数据,子组件使用了默认值Hello World,渲染的结果和第一个例子是一样的。

 

 源码分析


 以上面的例1为例,Vue.component()注册组件的时候会调用Vue.extend()生成一个Vue基础构造器,内部会调用mergeOptions函数合并属性, mergeOptions又会调用normalizeProps对props的属性进行一些规范化的修饰,如下:

  1. function normalizeProps (options, vm) { //第1361行 规范化props属性
  2. var props = options.props; //尝试获取props属性
  3. if (!props) { return }
  4. var res = {};
  5. var i, val, name;
  6. if (Array.isArray(props)) { //如果props是个数组 ;这是props的数组用法的分支
  7. i = props.length;
  8. while (i--) { //遍历props
  9. val = props[i];
  10. if (typeof val === 'string') { //如果值是一个字符串
  11. name = camelize(val);
  12. res[name] = { type: null }; //保存到res里面 ;例如:{ title: {type: null} }
  13. } else {
  14. warn('props must be strings when using array syntax.');
  15. }
  16. }
  17. } else if (isPlainObject(props)) { //如果props是个对象 ;这是props的对象用法的分支
  18. for (var key in props) {
  19. val = props[key];
  20. name = camelize(key);
  21. res[name] = isPlainObject(val)
  22. ? val
  23. : { type: val };
  24. }
  25. } else {
  26. warn(
  27. "Invalid value for option \"props\": expected an Array or an Object, " +
  28. "but got " + (toRawType(props)) + ".",
  29. vm
  30. );
  31. }
  32. options.props = res;
  33. }

 经过normalizeProps规范后,props被修饰为一个对象格式,例子里的执行到这里等于:

接下来_render函数执行遇到该组件时会执行createComponent函数,该函数又会执行extractPropsFromVNodeData(data, Ctor, tag)函数,如下:

  1. function extractPropsFromVNodeData ( //第2109行 获取原始值
  2. data,
  3. Ctor,
  4. tag
  5. ) {
  6. // we are only extracting raw values here.
  7. // validation and default values are handled in the child
  8. // component itself.
  9. var propOptions = Ctor.options.props; //获取组件的定义的props对象,例如:{message: {type: null}}
  10. if (isUndef(propOptions)) {
  11. return
  12. }
  13. var res = {};
  14. var attrs = data.attrs; //获取data的attrs属性,例如:{title: "Hello Vue"}
  15. var props = data.props; //获取data的props属性,这应该是建立父子组件时的关系
  16. if (isDef(attrs) || isDef(props)) { //如果data有定义了attrs或者props属性
  17. for (var key in propOptions) { //遍历组件的props属性
  18. var altKey = hyphenate(key);
  19. {
  20. var keyInLowerCase = key.toLowerCase(); //hyphenate:如果key是是驼峰字符串,则转换为-格式
  21. if (
  22. key !== keyInLowerCase &&
  23. attrs && hasOwn(attrs, keyInLowerCase) //转换为小写格式
  24. ) {
  25. tip(
  26. "Prop \"" + keyInLowerCase + "\" is passed to component " +
  27. (formatComponentName(tag || Ctor)) + ", but the declared prop name is" +
  28. " \"" + key + "\". " +
  29. "Note that HTML attributes are case-insensitive and camelCased " +
  30. "props need to use their kebab-case equivalents when using in-DOM " +
  31. "templates. You should probably use \"" + altKey + "\" instead of \"" + key + "\"."
  32. );
  33. }
  34. }
  35. checkProp(res, props, key, altKey, true) || //调用checkProp优先从props里拿对应的属性,其次从attrs里拿(对于attrs的话第五个参数为false,即会删除对应的attrs里的属性)
  36. checkProp(res, attrs, key, altKey, false);
  37. }
  38. }
  39. return res
  40. }

checkProp是检测props或attrs是否含有key对应的值,如下:

  1. function checkProp ( //第2150行 检测prop是否存在
  2. res,
  3. hash,
  4. key,
  5. altKey,
  6. preserve
  7. ) {
  8. if (isDef(hash)) { //如果hash存在
  9. if (hasOwn(hash, key)) { //如果hash里面有定义了key
  10. res[key] = hash[key];
  11. if (!preserve) {
  12. delete hash[key];
  13. }
  14. return true
  15. } else if (hasOwn(hash, altKey)) { //如果有驼峰的表示法,也找到了
  16. res[key] = hash[altKey];
  17. if (!preserve) {
  18. delete hash[altKey];
  19. }
  20. return true
  21. }
  22. }
  23. return false //如果在res里未找到则返回false
  24. }

 extractPropsFromVNodeData只是获取值,验证理验证和默认值是子组件完成执行的,执行到这里就获取到了props的值,例子里执行到这里等于

整个对象会作为propsData属性保存到组件的VNode里面,如下:

 当子组件实例化的时候会执行_init()函数,首先会执行initInternalComponent函数,对于props的操作如下:

  1. function initInternalComponent (vm, options) { //第4632行 子组件初始化子组件
  2. var opts = vm.$options = Object.create(vm.constructor.options); //组件的配置信息
  3. // doing this because it's faster than dynamic enumeration.
  4. var parentVnode = options._parentVnode; //该组件的占位符VNode
  5. opts.parent = options.parent;
  6. opts._parentVnode = parentVnode;
  7. opts._parentElm = options._parentElm;
  8. opts._refElm = options._refElm;
  9. var vnodeComponentOptions = parentVnode.componentOptions; //占位符VNode初始化传入的配置信息
  10. opts.propsData = vnodeComponentOptions.propsData; //这就是上面经过extractPropsFromVNodeData()得到的propsData对象
  11. opts._parentListeners = vnodeComponentOptions.listeners;
  12. opts._renderChildren = vnodeComponentOptions.children;
  13. opts._componentTag = vnodeComponentOptions.tag;
  14. if (options.render) {
  15. opts.render = options.render;
  16. opts.staticRenderFns = options.staticRenderFns;
  17. }
  18. }

这样组件实例化时就得到了propsData了,如下

然后回到_init()初始化函数,会执行initState()函数,该函数首先会判断是否有props属性,如果有则执行initProps初始化props,如下:

  1. function initProps (vm, propsOptions) { //第3319行 初始化props属性
  2. var propsData = vm.$options.propsData || {}; //获取propsData属性,也就是例子里的{title:"Hello World"}
  3. var props = vm._props = {};
  4. // cache prop keys so that future props updates can iterate using Array
  5. // instead of dynamic object key enumeration.
  6. var keys = vm.$options._propKeys = []; //用于保存当前组件的props里的key ;以便之后在父组件更新props时可以直接使用数组迭代,而不需要动态枚举键值
  7. var isRoot = !vm.$parent;
  8. // root instance props should be converted
  9. if (!isRoot) {
  10. toggleObserving(false);
  11. }
  12. var loop = function ( key ) { //定义一个loop函数,一会儿会循环调用它
  13. keys.push(key); //保存key
  14. var value = validateProp(key, propsOptions, propsData, vm); //执行validateProp检查propsData里的key值是否符合propsOptions里对应的要求,并将值保存到value里面
  15. /* istanbul ignore else */
  16. {
  17. var hyphenatedKey = hyphenate(key);
  18. if (isReservedAttribute(hyphenatedKey) ||
  19. config.isReservedAttr(hyphenatedKey)) {
  20. warn(
  21. ("\"" + hyphenatedKey + "\" is a reserved attribute and cannot be used as component prop."),
  22. vm
  23. );
  24. }
  25. defineReactive(props, key, value, function () { //将key变成响应式,同时也定义了props的key属性的值为value
  26. if (vm.$parent && !isUpdatingChildComponent) {
  27. warn(
  28. "Avoid mutating a prop directly since the value will be " +
  29. "overwritten whenever the parent component re-renders. " +
  30. "Instead, use a data or computed property based on the prop's " +
  31. "value. Prop being mutated: \"" + key + "\"",
  32. vm
  33. );
  34. }
  35. });
  36. }
  37. // static props are already proxied on the component's prototype
  38. // during Vue.extend(). We only need to proxy props defined at
  39. // instantiation here.
  40. if (!(key in vm)) {
  41. proxy(vm, "_props", key);
  42. }
  43. };
  44. for (var key in propsOptions) loop( key ); //遍历每个props 依次调用loop()函数
  45. toggleObserving(true);
  46. }

至此整个流程跑完了,前面说了extractPropsFromVNodeData只是获取值,而验证理验证和默认值就是在validateProp()函数内做的判断,如下:

  1. function validateProp ( //第1582行 检查props
  2. key,
  3. propOptions,
  4. propsData,
  5. vm
  6. ) {
  7. var prop = propOptions[key]; //获取对应的值,例如:{type: null}
  8. var absent = !hasOwn(propsData, key); //如果propsData没有key这个键名,则absent为true
  9. var value = propsData[key]; //尝试获取propsData里key这个键的值
  10. // boolean casting
  11. var booleanIndex = getTypeIndex(Boolean, prop.type); //调用getTypeIndex()含糊判断prop.type是否包含布尔类型
  12. if (booleanIndex > -1) {
  13. if (absent && !hasOwn(prop, 'default')) {
  14. value = false;
  15. } else if (value === '' || value === hyphenate(key)) {
  16. // only cast empty string / same name to boolean if
  17. // boolean has higher priority
  18. var stringIndex = getTypeIndex(String, prop.type);
  19. if (stringIndex < 0 || booleanIndex < stringIndex) {
  20. value = true;
  21. }
  22. }
  23. }
  24. // check default value
  25. if (value === undefined) { //如果value未定义
  26. value = getPropDefaultValue(vm, prop, key); //尝试获取默认值
  27. // since the default value is a fresh copy,
  28. // make sure to observe it.
  29. var prevShouldObserve = shouldObserve;
  30. toggleObserving(true);
  31. observe(value);
  32. toggleObserving(prevShouldObserve);
  33. }
  34. {
  35. assertProp(prop, key, value, vm, absent); //判断Prop是否有效
  36. }
  37. return value //最后返回value
  38. }

剩下来就几个工具函数了,比较简单,大致如此。

注:在Vue这么多属性里面,props是最有意思,最好玩的。虽然props的用法比较简单,但是它的原理实现我觉得是最复杂的,理解了props的实现原理,可以说是对Vue源码算是有比较大的深入了解了

原文链接:http://www.cnblogs.com/greatdesert/p/11090143.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号