彻底理解react中的props

每天对自己多问几个为什么,总是有着想象不到的收获。 一个菜鸟小白的成长之路(copyer)


​在react中 state 和 props是两个非常重要的属性,并且常用的属性。简单来说:都是用来保存数据状态的。

需注意的是state,props的每一次改动都会使页面重新渲染一次。


state的解释

state 意为 状态,组件内部的状态。

state是一个可变的属性,控制组件内部的状态

state 一般用于组件内部的状态维护,更新组建内部的数据,状态,更新子组件的props等


props的解释

PropsProperties的简写。

Props用于组件之间的通信,一种数据传递的方式。由于React的数据流是自上而下的,所以是从父组件向子组件进行传递。

props是只读属性。想要修改props,只能从父组件修改,然后传递给子组件。


props的形成

都知道, react 中的 jsx 会通过 babel转化成 createElement函数。所以,这里主要就是要知道createElement这个函数。话不多说,先看实例,然后看createElement的源码。

jsx转化成createElement的实例(babel官网的转化)

在这里插入图片描述

根据上面的转化和图解:

可以看,createElement 接受多个参数,

第一个参数为组件的名称(大写字母就是组件的名称,小写字母开头,就是原生的html标签),

第二个参数为props,如果没有props,就是为 null

第三个参数及跟多的参数(可选参数)

  • 如果没有子元素,就是undefined
  • 如果有子元素,那么子元素的createElement的函数就作为参数,如果有多个子元素,就依次排列下去(跟vue3的h函数有点区别,h函数的三个参数是数组,存放子元素的h函数)

看完了上面的分析,是不是对React.createElement函数有点认识了啊,那么接下来看看源码是怎么实现的吧。(这个函数不复杂,但是我还是省略一些代码,便于理解)

const RESERVED_PROPS = {
    
        // 处理props, 排除特殊的关键字 key ref 等等
  key: true,
  ref: true,
  __self: true,
  __source: true,
};

export function createElement(type, config, children) {
    
    
  let propName;

  const props = {
    
    };   //  定义一个props的空对象
    
    
  //... 省略代码
    
    
  // 遍历 config 赋值给 props
  for (propName in config) {
    
      
        if (
            hasOwnProperty.call(config, propName) &&  // propsName在config中
            !RESERVED_PROPS.hasOwnProperty(propName)  // propsName不是关键词
        ) {
    
    
            props[propName] = config[propName];
        }
    }
  }

  // 获取第二个参数后面的参数个数 
  const childrenLength = arguments.length - 2;
  if (childrenLength === 1) {
    
       // 如果长度为1,就是一个ReactElement
    props.children = children;
  } else if (childrenLength > 1) {
    
      // 如果长度为1, 就是一个数组,保存着多个ReactElement
    const childArray = Array(childrenLength);
    for (let i = 0; i < childrenLength; i++) {
    
    
      childArray[i] = arguments[i + 2];
    }
    // ...
    props.children = childArray;
  }

  return ReactElement( // 生成一个ReactElement的虚拟DOM
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}

看望上面的代码,就可以很清楚的了解props的组成。

  • let props = {} props是一个对象
  • propName in config 循环createElement的第二个参数config,第二个参数,就是组件中写的props
  • props.children 就是组件中是否存在子元素。如果没有子元素,值就是undefined;如果是有一个子元素,值就是ReactElement元素;如果有多个,值就是一个数组,数组中存着ReactElement元素

props.children

上面的源码解析中已经中分析了,可以继续看上面的分析


defaultProps

在定义中组件的时候,我们可以给组件的props设置默认值,可以有效的防止没有传递对应的props,是程序报错

具体用法:

// 定义组件
const Test = (props) => {
    
    
  const {
    
     title } = props
  return (
    <div>{
    
    title}</div>
  )
}
Test.defaultProps = {
    
    
  title: 'james'
}

//使用组件
const App = () => {
    
    
    return (
    	<div>
        	<Test title="kobe" />  
        </div>
    )
}

在上面使用Test组件的时候,如果传递了title,那么Test中,title就是kobe。如果没有传递title,那么组件中的title就是james


源码分析

createElement中的一个参数,就是组件的名称(type)

// 源码部分处理默认值
if (type && type.defaultProps) {
    
    
    const defaultProps = type.defaultProps;
    for (propName in defaultProps) {
    
        // 循环默认props,对象
      if (props[propName] === undefined) {
    
       // 如果在props中的值为undefined,使用默认值
        props[propName] = defaultProps[propName];
      }
    }
  }

displayName

displayName:就是组件取个名字,便于区分。

function Test() {
    
    
    
}
Test.displayName = 'OwnTest'   // OwnTest用于展示

用途:

  • 在react的设置设计模式中的组合模式,就需要通过判断传递的子组件来进行渲染。
  • 调试工具的调试的名称显示

PropTypes

对组件的props的类型限定。如果JavaScript是个弱语言,会自动进行隐式转化,容易造成react的程序报错。所以,我们就需要对类型进行限定。

所以,prop-types 就很好对组件的类名进行限定。

安装:

npm install --save prop-types

官网地址:

https://zh-hans.reactjs.org/docs/typechecking-with-proptypes.html#gatsby-focus-wrapper

示例:

import PropTypes from 'prop-types';

class Greeting extends React.Component {
    
    
  render() {
    
    
    return (
      <h1>Hello, {
    
    this.props.name}</h1>
    );
  }
}

Greeting.propTypes = {
    
    
  name: PropTypes.string
};

类型限定:

import PropTypes from 'prop-types';

MyComponent.propTypes = {
    
    
  // 你可以将属性声明为 JS 原生类型,默认情况下
  // 这些属性都是可选的。
  optionalArray: PropTypes.array,
  optionalBool: PropTypes.bool,
  optionalFunc: PropTypes.func,
  optionalNumber: PropTypes.number,
  optionalObject: PropTypes.object,
  optionalString: PropTypes.string,
  optionalSymbol: PropTypes.symbol,

  // 任何可被渲染的元素(包括数字、字符串、元素或数组)
  // (或 Fragment) 也包含这些类型。
  optionalNode: PropTypes.node,

  // 一个 React 元素。
  optionalElement: PropTypes.element,

  // 一个 React 元素类型(即,MyComponent)。
  optionalElementType: PropTypes.elementType,

  // 你也可以声明 prop 为类的实例,这里使用
  // JS 的 instanceof 操作符。
  optionalMessage: PropTypes.instanceOf(Message),

  // 你可以让你的 prop 只能是特定的值,指定它为
  // 枚举类型。
  optionalEnum: PropTypes.oneOf(['News', 'Photos']),

  // 一个对象可以是几种类型中的任意一个类型
  optionalUnion: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.number,
    PropTypes.instanceOf(Message)
  ]),

  // 可以指定一个数组由某一类型的元素组成
  optionalArrayOf: PropTypes.arrayOf(PropTypes.number),

  // 可以指定一个对象由某一类型的值组成
  optionalObjectOf: PropTypes.objectOf(PropTypes.number),

  // 可以指定一个对象由特定的类型值组成
  optionalObjectWithShape: PropTypes.shape({
    
    
    color: PropTypes.string,
    fontSize: PropTypes.number
  }),

  // An object with warnings on extra properties
  optionalObjectWithStrictShape: PropTypes.exact({
    
    
    name: PropTypes.string,
    quantity: PropTypes.number
  }),

  // 你可以在任何 PropTypes 属性后面加上 `isRequired` ,确保
  // 这个 prop 没有被提供时,会打印警告信息。
  requiredFunc: PropTypes.func.isRequired,

  // 任意类型的必需数据
  requiredAny: PropTypes.any.isRequired,

  // 你可以指定一个自定义验证器。它在验证失败时应返回一个 Error 对象。
  // 请不要使用 `console.warn` 或抛出异常,因为这在 `oneOfType` 中不会起作用。
  customProp: function(props, propName, componentName) {
    
    
    if (!/matchme/.test(props[propName])) {
    
    
      return new Error(
        'Invalid prop `' + propName + '` supplied to' +
        ' `' + componentName + '`. Validation failed.'
      );
    }
  },

  // 你也可以提供一个自定义的 `arrayOf` 或 `objectOf` 验证器。
  // 它应该在验证失败时返回一个 Error 对象。
  // 验证器将验证数组或对象中的每个值。验证器的前两个参数
  // 第一个是数组或对象本身
  // 第二个是他们当前的键。
  customArrayProp: PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) {
    
    
    if (!/matchme/.test(propValue[key])) {
    
    
      return new Error(
        'Invalid prop `' + propFullName + '` supplied to' +
        ' `' + componentName + '`. Validation failed.'
      );
    }
  })
};

哈哈哈,上面的有点多,不用记太多,记住常用的就行了。如果有特殊的需求,就再去查官网吧。


TypeScript对props的限定

在上面,就是对props的类型限定和默认值的设置,在ts中就能完美的解决。

类组件

React.Component<P, S>

P: 就是对props的类型进行限定。

S:就是对state的类型限定

// node_modules/@types/react/index.d.ts

class Component<P, S> {
    
    
        //设置静态属性
        static contextType?: Context<any>;
        context: any;
        //限定props的类型,为readonly类型,只读的
        constructor(props: Readonly<P>);
       
        constructor(props: P, context?: any);
        
        setState<K extends keyof S>(
            state: ((prevState: Readonly<S>, props: Readonly<P>) => (Pick<S, K> | S | null)) | (Pick<S, K> | S | null),
            callback?: () => void
        ): void;

        forceUpdate(callback?: () => void): void;
        
        //render的类型
        render(): ReactNode;
        
        //对props的类型限定
        readonly props: Readonly<P> & Readonly<{
    
     children?: ReactNode }>;
        
        //state的类型限定
        state: Readonly<S>;
        
        refs: {
    
    
            [key: string]: ReactInstance
        };
    }

函数组件

React.FC<IProps>

IProps: 就是对props的类型进行限定

type FC<P = {
    
    }> = FunctionComponent<P>;

interface FunctionComponent<P = {
    
    }> {
    
    
    
    //对props进行限定, 返回值是一个ReactElement对象,为了生成虚拟DOM
    (props: PropsWithChildren<P>, context?: any): ReactElement | null;
    propTypes?: WeakValidationMap<P>;
    contextTypes?: ValidationMap<any>;
    defaultProps?: Partial<P>;
    displayName?: string;
}

props的类型 :PropsWithChildren<P>

这里PropsWithChildren中内部实现了props新增children属性。


总结

​ 目前对props的所有认识,就在这里,以后遇到新的知识,然后在进行补充。如果上面的有错误,可以提出来哟。


附加信息:createElement的完整源码

/**
 * Create and return a new ReactElement of the given type.
 * See https://reactjs.org/docs/react-api.html#createelement
 */
export function createElement(type, config, children) {
    
    
  let propName;

  // Reserved names are extracted
  const props = {
    
    };

  let key = null;
  let ref = null;
  let self = null;
  let source = null;

  if (config != null) {
    
    
    if (hasValidRef(config)) {
    
    
      ref = config.ref;

      if (__DEV__) {
    
    
        warnIfStringRefCannotBeAutoConverted(config);
      }
    }
    if (hasValidKey(config)) {
    
    
      key = '' + config.key;
    }

    self = config.__self === undefined ? null : config.__self;
    source = config.__source === undefined ? null : config.__source;
    // Remaining properties are added to a new props object
    for (propName in config) {
    
    
      if (
        hasOwnProperty.call(config, propName) &&
        !RESERVED_PROPS.hasOwnProperty(propName)  // reserved_props
      ) {
    
    
        props[propName] = config[propName];
      }
    }
  }

  // Children can be more than one argument, and those are transferred onto
  // the newly allocated props object.
  const childrenLength = arguments.length - 2;
  if (childrenLength === 1) {
    
    
    props.children = children;
  } else if (childrenLength > 1) {
    
    
    const childArray = Array(childrenLength);
    for (let i = 0; i < childrenLength; i++) {
    
    
      childArray[i] = arguments[i + 2];
    }
    if (__DEV__) {
    
    
      if (Object.freeze) {
    
    
        Object.freeze(childArray);
      }
    }
    props.children = childArray;
  }

  // Resolve default props
  if (type && type.defaultProps) {
    
    
    const defaultProps = type.defaultProps;
    for (propName in defaultProps) {
    
    
      if (props[propName] === undefined) {
    
    
        props[propName] = defaultProps[propName];
      }
    }
  }
  if (__DEV__) {
    
    
    if (key || ref) {
    
    
      const displayName =
        typeof type === 'function'
          ? type.displayName || type.name || 'Unknown'
          : type;
      if (key) {
    
    
        defineKeyPropWarningGetter(props, displayName);
      }
      if (ref) {
    
    
        defineRefPropWarningGetter(props, displayName);
      }
    }
  }
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}

猜你喜欢

转载自blog.csdn.net/James_xyf/article/details/120775014