父組件經過props屬性向子組件傳遞數據,定義組件的時候能夠定義一個props屬性,值能夠是一個字符串數組或一個對象。html
例如:vue
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> </head> <body> <div id="app"><child :title="message"></child></div> <script> Vue.component('child',{ template:'<h1>{{title}}</h1>',props:['title'] //這裏props是一個字符串數組 }) var app = new Vue({ el:'#app',data:{message:'Hello World'} }) </script> </body> </html>
這裏咱們給child這個組件定義了名爲title的props,父組件經過title特性傳遞給子組件,渲染爲:node
props除了數組,也能夠是一個對象,此時對象的鍵對應的props的名稱,值又是一個對象,能夠包含以下屬性:npm
type: ;類型,能夠設置爲:String、Number、Boolean、Array、Object、Date等等 ;若是隻設置type而未設置其餘選項,則值能夠直接用類型,例如:props:{title:Object}
default ;默認值
required ;布爾類型,表示是否必填項目
validator ;自定義驗證函數 數組
例如:app
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> <title>Document</title> </head> <body> <div id="app"><child></child></div> <script> Vue.component('child',{ template:'<h1>{{title}}</h1>',props:{title:{default:'Hello World'}} //這裏咱們定義的title是個對象,含有默認值 }) var app = new Vue({ el:'#app' }) </script> </body> </html>
這裏父組件app沒有給子組件child傳遞數據,子組件使用了默認值Hello World,渲染的結果和第一個例子是同樣的。函數
源碼分析工具
以上面的例1爲例,Vue.component()註冊組件的時候會調用Vue.extend()生成一個Vue基礎構造器,內部會調用mergeOptions函數合併屬性, mergeOptions又會調用normalizeProps對props的屬性進行一些規範化的修飾,以下:oop
function normalizeProps (options, vm) { //第1361行 規範化props屬性 var props = options.props; //嘗試獲取props屬性 if (!props) { return } var res = {}; var i, val, name; if (Array.isArray(props)) { //若是props是個數組 ;這是props的數組用法的分支 i = props.length; while (i--) { //遍歷props val = props[i]; if (typeof val === 'string') { //若是值是一個字符串 name = camelize(val); res[name] = { type: null }; //保存到res裏面 ;例如:{ title: {type: null} } } else { warn('props must be strings when using array syntax.'); } } } else if (isPlainObject(props)) { //若是props是個對象 ;這是props的對象用法的分支 for (var key in props) { val = props[key]; name = camelize(key); res[name] = isPlainObject(val) ? val : { type: val }; } } else { warn( "Invalid value for option \"props\": expected an Array or an Object, " + "but got " + (toRawType(props)) + ".", vm ); } options.props = res; }
通過normalizeProps規範後,props被修飾爲一個對象格式,例子裏的執行到這裏等於:源碼分析
接下來_render函數執行遇到該組件時會執行createComponent函數,該函數又會執行extractPropsFromVNodeData(data, Ctor, tag)函數,以下:
function extractPropsFromVNodeData ( //第2109行 獲取原始值 data, Ctor, tag ) { // we are only extracting raw values here. // validation and default values are handled in the child // component itself. var propOptions = Ctor.options.props; //獲取組件的定義的props對象,例如:{message: {type: null}} if (isUndef(propOptions)) { return } var res = {}; var attrs = data.attrs; //獲取data的attrs屬性,例如:{title: "Hello Vue"} var props = data.props; //獲取data的props屬性,這應該是創建父子組件時的關係 if (isDef(attrs) || isDef(props)) { //若是data有定義了attrs或者props屬性 for (var key in propOptions) { //遍歷組件的props屬性 var altKey = hyphenate(key); { var keyInLowerCase = key.toLowerCase(); //hyphenate:若是key是是駝峯字符串,則轉換爲-格式 if ( key !== keyInLowerCase && attrs && hasOwn(attrs, keyInLowerCase) //轉換爲小寫格式 ) { tip( "Prop \"" + keyInLowerCase + "\" is passed to component " + (formatComponentName(tag || Ctor)) + ", but the declared prop name is" + " \"" + key + "\". " + "Note that HTML attributes are case-insensitive and camelCased " + "props need to use their kebab-case equivalents when using in-DOM " + "templates. You should probably use \"" + altKey + "\" instead of \"" + key + "\"." ); } } checkProp(res, props, key, altKey, true) || //調用checkProp優先從props裏拿對應的屬性,其次從attrs裏拿(對於attrs的話第五個參數爲false,即會刪除對應的attrs裏的屬性) checkProp(res, attrs, key, altKey, false); } } return res }
checkProp是檢測props或attrs是否含有key對應的值,以下:
function checkProp ( //第2150行 檢測prop是否存在 res, hash, key, altKey, preserve ) { if (isDef(hash)) { //若是hash存在 if (hasOwn(hash, key)) { //若是hash裏面有定義了key res[key] = hash[key]; if (!preserve) { delete hash[key]; } return true } else if (hasOwn(hash, altKey)) { //若是有駝峯的表示法,也找到了 res[key] = hash[altKey]; if (!preserve) { delete hash[altKey]; } return true } } return false //若是在res裏未找到則返回false }
extractPropsFromVNodeData只是獲取值,驗證理驗證和默認值是子組件完成執行的,執行到這裏就獲取到了props的值,例子裏執行到這裏等於
整個對象會做爲propsData屬性保存到組件的VNode裏面,以下:
當子組件實例化的時候會執行_init()函數,首先會執行initInternalComponent函數,對於props的操做以下:
function initInternalComponent (vm, options) { //第4632行 子組件初始化子組件 var opts = vm.$options = Object.create(vm.constructor.options); //組件的配置信息 // doing this because it's faster than dynamic enumeration. var parentVnode = options._parentVnode; //該組件的佔位符VNode opts.parent = options.parent; opts._parentVnode = parentVnode; opts._parentElm = options._parentElm; opts._refElm = options._refElm; var vnodeComponentOptions = parentVnode.componentOptions; //佔位符VNode初始化傳入的配置信息 opts.propsData = vnodeComponentOptions.propsData; //這就是上面通過extractPropsFromVNodeData()獲得的propsData對象 opts._parentListeners = vnodeComponentOptions.listeners; opts._renderChildren = vnodeComponentOptions.children; opts._componentTag = vnodeComponentOptions.tag; if (options.render) { opts.render = options.render; opts.staticRenderFns = options.staticRenderFns; } }
這樣組件實例化時就獲得了propsData了,以下
而後回到_init()初始化函數,會執行initState()函數,該函數首先會判斷是否有props屬性,若是有則執行initProps初始化props,以下:
function initProps (vm, propsOptions) { //第3319行 初始化props屬性 var propsData = vm.$options.propsData || {}; //獲取propsData屬性,也就是例子裏的{title:"Hello World"} var props = vm._props = {}; // cache prop keys so that future props updates can iterate using Array // instead of dynamic object key enumeration. var keys = vm.$options._propKeys = []; //用於保存當前組件的props裏的key ;以便以後在父組件更新props時能夠直接使用數組迭代,而不須要動態枚舉鍵值 var isRoot = !vm.$parent; // root instance props should be converted if (!isRoot) { toggleObserving(false); } var loop = function ( key ) { //定義一個loop函數,一下子會循環調用它 keys.push(key); //保存key var value = validateProp(key, propsOptions, propsData, vm); //執行validateProp檢查propsData裏的key值是否符合propsOptions裏對應的要求,並將值保存到value裏面 /* istanbul ignore else */ { var hyphenatedKey = hyphenate(key); if (isReservedAttribute(hyphenatedKey) || config.isReservedAttr(hyphenatedKey)) { warn( ("\"" + hyphenatedKey + "\" is a reserved attribute and cannot be used as component prop."), vm ); } defineReactive(props, key, value, function () { //將key變成響應式,同時也定義了props的key屬性的值爲value if (vm.$parent && !isUpdatingChildComponent) { warn( "Avoid mutating a prop directly since the value will be " + "overwritten whenever the parent component re-renders. " + "Instead, use a data or computed property based on the prop's " + "value. Prop being mutated: \"" + key + "\"", vm ); } }); } // static props are already proxied on the component's prototype // during Vue.extend(). We only need to proxy props defined at // instantiation here. if (!(key in vm)) { proxy(vm, "_props", key); } }; for (var key in propsOptions) loop( key ); //遍歷每一個props 依次調用loop()函數 toggleObserving(true); }
至此整個流程跑完了,前面說了extractPropsFromVNodeData只是獲取值,而驗證理驗證和默認值就是在validateProp()函數內作的判斷,以下:
function validateProp ( //第1582行 檢查props key, propOptions, propsData, vm ) { var prop = propOptions[key]; //獲取對應的值,例如:{type: null} var absent = !hasOwn(propsData, key); //若是propsData沒有key這個鍵名,則absent爲true var value = propsData[key]; //嘗試獲取propsData裏key這個鍵的值 // boolean casting var booleanIndex = getTypeIndex(Boolean, prop.type); //調用getTypeIndex()含糊判斷prop.type是否包含布爾類型 if (booleanIndex > -1) { if (absent && !hasOwn(prop, 'default')) { value = false; } else if (value === '' || value === hyphenate(key)) { // only cast empty string / same name to boolean if // boolean has higher priority var stringIndex = getTypeIndex(String, prop.type); if (stringIndex < 0 || booleanIndex < stringIndex) { value = true; } } } // check default value if (value === undefined) { //若是value未定義 value = getPropDefaultValue(vm, prop, key); //嘗試獲取默認值 // since the default value is a fresh copy, // make sure to observe it. var prevShouldObserve = shouldObserve; toggleObserving(true); observe(value); toggleObserving(prevShouldObserve); } { assertProp(prop, key, value, vm, absent); //判斷Prop是否有效 } return value //最後返回value }
剩下來就幾個工具函數了,比較簡單,大體如此。
注:在Vue這麼多屬性裏面,props是最有意思,最好玩的。雖然props的用法比較簡單,可是它的原理實現我以爲是最複雜的,理解了props的實現原理,能夠說是對Vue源碼算是有比較大的深刻了解了