zoukankan      html  css  js  c++  java
  • 从vue源码看props

    前言

    平时写vue的时候知道props有很多种用法,今天我们来看看vue内部是怎么处理props中那么多的用法的。

    vue提供的props的用法

    1. 数组形式

    props: ['name', 'value']
    

    2. 对象形式

    对象形式内部也提供了三种写法:

    props: {
        // 基础的类型检查
        name: String,
        // 多个可能的类型
        value: [String, Number],
        // 对象形式
        id: {
            type: Number,
            required: true
        }
    }
    

    props实现的原理

    function normalizeProps (options: Object, vm: ?Component) {
      const props = options.props
      if (!props) return
      const res = {}
      let i, val, name
      if (Array.isArray(props)) {
        ...
      } else if (isPlainObject(props)) {
        ...
      } else if (process.env.NODE_ENV !== 'production') {
        ...
      }
      options.props = res
    }
    

    normalizeProps函数就是vue实际处理props的地方,从函数名的翻译我们可以看出该函数的功能就是标准化props的值。该函数主要分成3部分:① 从options对象中获取props的值并且定义一个res空对象;②几个if ... else,分别根据props值的不同类型来处理res对象;③ 用处理后的res对象覆盖原来options对象的props属性的值。

    接下来看看那几个if ... else的代码:

    if (Array.isArray(props)) {
        i = props.length
        while (i--) {
          val = props[i]
          if (typeof val === 'string') {
            name = camelize(val)
            res[name] = { type: null }
          } else if (process.env.NODE_ENV !== 'production') {
            warn('props must be strings when using array syntax.')
          }
        }
      }
    

    这个代码实际就是处理props的值为数组的情况,例如: props: ['name', 'value']。使用while遍历该数组,如果数组内元素的类型不是字符串并且不是生产环境,那么就抛错:‘props的值类型为数组时,数组里面的元素的类型就必须是字符串’。如果是字符串的情况下,使用camelize函数处理一下val的值,并且赋值给name变量。这里的camelize函数的实际作用就是将'-'转换为驼峰。camelize函数具体的实现方式在后面分析。然后在res对象上面添加一个为name变量的属性,该属性的值为空对象 { type: null }

    props: ['name', 'value']这种写法经过上面的处理后就会变成了下面这样:

    props: {
        name: {
            type: null
        },
        value: {
            type: null
        }
    }
    

    接下来看看下面这个else if(isPlainObject(props)),这里的isPlainObject函数实际就是返回props的值是否为objectisPlainObject函数的具体实现我们也在后面分析。

    else if (isPlainObject(props)) {
       for (const key in props) {
         val = props[key]
         name = camelize(key)
         res[name] = isPlainObject(val)
           ? val
           : { type: val }
       }
     }
    

    使用for...in遍历props对象,和上面一样使用camelize函数将'-'转换为驼峰。这里有个三目运算:

    res[name] = isPlainObject(val) ? val : { type: val }
    

    判断了一下val如果是object,那么在res对象上面添加一个为name变量的属性,并且将该属性的值设置为val。这个其实就是处理下面这种props的写法:

    props: {
       // 对象形式
       id: {
           type: Number,
           required: true
       }
    }
    

    如果val不是object,那么也在res对象上面添加一个为name变量的属性,并且将该属性的值设置为{ type: val }。这个其实就是处理下面这种props的写法:

    props: {
        // 基础的类型检查
        name: String,
        // 多个可能的类型
        value: [String, Number],
    }
    

    经过处理后props会变成了下面这样:

    props: {
        name: {
            type: String
        },
        value: {
            type: [String, Number]
        }
    }
    

    所以不管我们使用vue提供的props哪种写法,最终vue都会帮我们转换成下面这种类型:

    props: {
        name: {
            ...,
            type: '类型'
        }
    }
    

    接下来看看上面提到的util函数isPlainObject,先把源码贴出来。

    const _toString = Object.prototype.toString
    
    export function isPlainObject (obj: any): boolean {
      return _toString.call(obj) === '[object Object]'
    }
    

    其实Object.prototype.toString.call(obj)的值为obj对象的类型。例如:

    Object.prototype.toString.call({a: 1})      // [object Object]
    Object.prototype.toString.call(new Date)    // [object Date]
    Object.prototype.toString.call([1])         // [object Array]
    Object.prototype.toString.call(null)        // [object Null]
    

    接下来看看上面提到的util函数camelize,还是先把源码贴出来。

    export function cached<F: Function> (fn: F): F {
      const cache = Object.create(null)
      return (function cachedFn (str: string) {
        const hit = cache[str]
        return hit || (cache[str] = fn(str))
      }: any)
    }
    
    const camelizeRE = /-(w)/g
    export const camelize = cached((str: string): string => {
      return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '')
    })
    

    这里定义了两个函数,分别是cachedcamelize,其中camelize就是我们上面调用的,cached是在camelize函数内部调用的。

    我们先来看看camelize函数,其实camelize函数就是执行cached后返回的一个函数。调用cached时传入了一个箭头函数,箭头函数内部是调用了正则的replace方法,将传入的str变量中匹配/-(w)/g的变成大写字母,并且返回replace后的值。(也就是将-转换成驼峰)。

    再来看看cached函数,该函数传入的变量其实就是camelize那里的箭头函数,首先定义了一个cache空对象,然后直接返回了cachedFn函数。我们在外部调用camelize(key)时,其实就是执行了这里的了cachedFn函数,str的值就是传入的key的值。很明显这里是一个闭包,可以在外部调用camelize 函数的时候可以修改或者读取这里定义的cache 对象的值。获取cache 对象中keystr变量值的属性值赋值给hit变量。如果有hit变量的值,那么就直接返回hit的值,如果没有就执行camelize 传入的箭头函数,并且将箭头函数的返回值赋值给catche对象的str属性。如果下次调用camelize 函数时传入了相同的str,那么就不会执行箭头函数,直接返回闭包中的cache对象的str属性的值。这里是性能优化的一种手段。

    例如:第一次调用 camelize('name')后,cache对象的值就变成了{name: 'name'}。然后在其他地方再次调用 camelize('name')时再次执行cachedFn函数,此时hit变量的值为'name'。直接返回hit变量的值,不会执行传入的箭头函数。

  • 相关阅读:
    deferred 对象
    JVM--------3
    JVM类加载机制————2
    JVM加载的初始化类
    补充==的使用和equals的区别
    MyBatis_SelectKey使用oracle 序列插入主键
    MySql_ procedure
    mysql function
    jsonp _____跨域请求实现
    shell(shell变量、条件表达式、流程控制)
  • 原文地址:https://www.cnblogs.com/heavenYJJ/p/10242084.html
Copyright © 2011-2022 走看看