【React】react源码梳理笔记(一)

前言

  • 该会的还是必须得会,千万不能怕麻烦。一步步拆开总能到终点。

实现虚拟dom

  • jsx语法都是通过babel转译完成。
  • 首先是个普通的类组件:
class App extends Component{
  render(){
    return(
      <div>
          <p>1</p>
          <button>+</button>
      </div>
    )
  }
}
  • 经转译后得到
class App extends Component {
  render() {
    return /*#__PURE__*/ React.createElement(
      "div",
      null,
      /*#__PURE__*/ React.createElement("p", null, "1"),
      /*#__PURE__*/ React.createElement("button", null, "+")
    );
  }
}
  • 分析下这里需要做2个东西,一个是Component,一个是React里做个createElement方法。
  • 看一下react源码里react是什么玩意?是类还是啥?答案是个对象,源码里这么写:
const React = {
  Children: {
    map,
    forEach,
    count,
    toArray,
    only,
  },

  createRef,
  Component,
  PureComponent,

  createContext,
  forwardRef,
  lazy,
  memo,

  Fragment: REACT_FRAGMENT_TYPE,
  StrictMode: REACT_STRICT_MODE_TYPE,
  Suspense: REACT_SUSPENSE_TYPE,

  createElement: __DEV__ ? createElementWithValidation : createElement,
  cloneElement: __DEV__ ? cloneElementWithValidation : cloneElement,
  createFactory: __DEV__ ? createFactoryWithValidation : createFactory,
  isValidElement: isValidElement,

  version: ReactVersion,

  __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: ReactSharedInternals,
};
  • 看一下createElement和component是个啥?
  • 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 (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)
        ) {
          props[propName] = config[propName];
        }
      }
    }
    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];
    }
    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];
      }
    }
  }
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}
  • component源码:
function Component(props, context, updater) {
  this.props = props;
  this.context = context;
  // If a component has string refs, we will assign a different object later.
  this.refs = emptyObject;
  // We initialize the default updater but the real one gets injected by the
  // renderer.
  this.updater = updater || ReactNoopUpdateQueue;
}

Component.prototype.isReactComponent = {};
Component.prototype.setState = function(partialState, callback) {
  invariant(
    typeof partialState === 'object' ||
      typeof partialState === 'function' ||
      partialState == null,
    'setState(...): takes an object of state variables to update or a ' +
      'function which returns an object of state variables.',
  );
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
Component.prototype.forceUpdate = function(callback) {
  this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};

function ComponentDummy() {}
ComponentDummy.prototype = Component.prototype;

/**
 * Convenience component with default shallow equality check for sCU.
 */
function PureComponent(props, context, updater) {
  this.props = props;
  this.context = context;
  // If a component has string refs, we will assign a different object later.
  this.refs = emptyObject;
  this.updater = updater || ReactNoopUpdateQueue;
}

const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());
pureComponentPrototype.constructor = PureComponent;
// Avoid an extra prototype jump for these methods.
Object.assign(pureComponentPrototype, Component.prototype);
pureComponentPrototype.isPureReactComponent = true;
  • Component 那个有点多,我画个图来梳理下Component到底是个啥

在这里插入图片描述

  • 其中,红色底色表示函数,绿色表示对象,浅灰色表示其公有属性,白色表示原型对象上的属性。
  • 为什么要设计成这样?其实就是个继承,但是为什么操作的这么骚?先不用管。先了解结构就行。
  • createElement可以发现它是返回的一个ReactElement,
  • 这个ReactElement源码:
const ReactElement = function(type, key, ref, self, source, owner, props) {
  const element = {
    // This tag allows us to uniquely identify this as a React Element
    $$typeof: REACT_ELEMENT_TYPE,

    // Built-in properties that belong on the element
    type: type,
    key: key,
    ref: ref,
    props: props,

    // Record the component responsible for creating this element.
    _owner: owner,
  };

  return element;
};
  • 所以createElement方法最终返回的是ReactElement创建出来的对象。
  • createElement主要是把传来的config里面几个关键的属性提取出来,分配孩子和属性,最后让ReactElement造出个对象。
  • 经分析,其中source和self生产模式无用。先可以精简成这样:
const RESERVED_PROPS = {//createElement单独挑出来的属性
    key: true,
    ref: true,
    __self: true,
    __source: true,
};
  
function hasValidRef(config) {
return config.ref !== undefined;
}
function hasValidKey(config) {
    return config.key !== undefined;
}
const ReactCurrentOwner={
    current:null
}
//有symbol并且有symbol.for方法
const hasSymbol = typeof Symbol === 'function' && Symbol.for;

const REACT_ELEMENT_TYPE = hasSymbol
? Symbol.for('react.element')
: 0xeac7;
const ReactElement = function(type, key, ref, owner,props) {
    const element = {
      // 通过symbol创建标识,没有symbol给个数字
      $$typeof: REACT_ELEMENT_TYPE,
    //剩余属性附上
      type: type,
      key: key,
      ref: ref,
      _owner: owner,
      props: props,
    };
    return element;
};


export function createElement(type, config, children) {
    let propName;
    const props = {};
  
    let key = null;
    let ref = null;
  
    if (config != null) {
        /////////挑出ref 和key 
      if (hasValidRef(config)) {
        ref = config.ref;
      }
      if (hasValidKey(config)) {
        key = '' + config.key;
      }
      // 剩余属性放入Props
      for (propName in config) {
        if (
          hasOwnProperty.call(config, propName) &&
          !RESERVED_PROPS.hasOwnProperty(propName)
        ) {
          props[propName] = config[propName];
        }
      }
    }
    //孩子长度等于参数总长减去开头2个
    const childrenLength = arguments.length - 2;
    if (childrenLength === 1) {//一个孩子就给children
        props.children = children;
    } else if (childrenLength > 1) {
        const childArray = Array(childrenLength);
        for (let i = 0; i < childrenLength; i++) {
            childArray[i] = arguments[i + 2];
        }
        props.children = childArray;//多个就变成数组
    }

    //如果存在标签的defaultProps属性(类的静态属性),并且这个属性config里没传,那么也赋给props
    if (type && type.defaultProps) {
        const defaultProps = type.defaultProps;
        for (propName in defaultProps) {
        if (props[propName] === undefined) {
            props[propName] = defaultProps[propName];
        }
        }
    }
    return ReactElement(
        type,
        key,
        ref,
        ReactCurrentOwner.current,
        props,//除了保留属性的其余属性 孩子在children上
    );
}
  • Component部分
const emptyObject = {};
export function Component(props, context, updater) {
    this.props = props;
    this.context = context;
    this.refs = emptyObject;
    this.updater = updater 
}
  
Component.prototype.isReactComponent = {};
Component.prototype.setState = function(partialState, callback) {
    this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
Component.prototype.forceUpdate = function(callback) {
    this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};

function ComponentDummy() {}
ComponentDummy.prototype = Component.prototype;
function PureComponent(props, context, updater) {
    this.props = props;
    this.context = context;
    this.refs = emptyObject;
    this.updater = updater 
}
const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());
pureComponentPrototype.constructor = PureComponent;
Object.assign(pureComponentPrototype, Component.prototype);
pureComponentPrototype.isPureReactComponent = true;
  • 这时候使用react改成自己的react ,就已经可以工作了!
import React,{Component} from './react';
import ReactDOM from 'react-dom';

class App extends Component {
  render() {
    return /*#__PURE__*/ React.createElement(
      "div",
      null,
      /*#__PURE__*/ React.createElement("p", null, "1"),
      /*#__PURE__*/ React.createElement("button", null, "+")
    );
  }
}
ReactDOM.render(
  <App></App>,
  document.getElementById('root')
);
  • 使用Jsx一样可以工作,因为是靠babel转译的语法。
  • 下面实现下React.Children.map这个方法。看一下官方怎么用的:
class Child extends Component {
  render() {
    console.log(this.props.children);
    const mappedChildren = React.Children.map(
      this.props.children,
      (item, index) => (
        [<div key={`div${index}A`}>{item}</div>, <div key={`div${index}B`}>{item}</div>]
      )
    );
    console.log(mappedChildren);
    return (
      <div>
        {mappedChildren}
      </div>
    )
  }
}
class App extends Component {
  render() {

    return (
      <Child>
        <div>child1</div>
        <div key="key2">child2</div>
        <div key="key3">child3</div>
        {
          [
            <div key="key4">child4</div>,
            <div key="key5">child5</div>,
            <div key="key6">child6</div>
          ]
        }
      </Child>
    )
  }
}
0: {$$typeof: Symbol(react.element), type: "div", key: ".0/.$div0A", ref: null, props: {…}, …}
1: {$$typeof: Symbol(react.element), type: "div", key: ".0/.$div0B", ref: null, props: {…}, …}
2: {$$typeof: Symbol(react.element), type: "div", key: ".$key2/.$div1A", ref: null, props: {…}, …}
3: {$$typeof: Symbol(react.element), type: "div", key: ".$key2/.$div1B", ref: null, props: {…}, …}
4: {$$typeof: Symbol(react.element), type: "div", key: ".$key3/.$div2A", ref: null, props: {…}, …}
5: {$$typeof: Symbol(react.element), type: "div", key: ".$key3/.$div2B", ref: null, props: {…}, …}
6: {$$typeof: Symbol(react.element), type: "div", key: ".3:$key4/.$div3A", ref: null, props: {…}, …}
7: {$$typeof: Symbol(react.element), type: "div", key: ".3:$key4/.$div3B", ref: null, props: {…}, …}
8: {$$typeof: Symbol(react.element), type: "div", key: ".3:$key5/.$div4A", ref: null, props: {…}, …}
9: {$$typeof: Symbol(react.element), type: "div", key: ".3:$key5/.$div4B", ref: null, props: {…}, …}
10: {$$typeof: Symbol(react.element), type: "div", key: ".3:$key6/.$div5A", ref: null, props: {…}, …}
11: {$$typeof: Symbol(react.element), type: "div", key: ".3:$key6/.$div5B", ref: null, props:
  • 可以发现,这个key有个特点,就是如果有key,那么使用key,否则就是索引,然后用/分割,下一级以.$开头。另外可以抹平数组。
  • 每下一层会加冒号
  • 自己可以实现下,主要是递归:
function judgeKey(children,index,keyindex){
    let key =keyindex
    if(children.key){
        key= key+`$${children.key}`
    }else{
        key=key+`${index}`
    }
    return key
}
function judgeSecond(key){
    let reg = new RegExp('/.')
   return reg.exec(key)
}

function mapChildren(children,func){
    let res = []
    let index=0
    function mapAll(children,key){
        for(let i  in  children){
            if(children.hasOwnProperty(i)){
                if(Array.isArray(children[i])){
                    let newkey = key+i+`:`
                    mapAll(children[i],newkey)
                }else{
                    let newkey = judgeKey(children[i],i,key)               
                    if(judgeSecond(newkey)){//只可能不是array
                        children[i].key =newkey
                        res.push(children[i])  
                    }else{
                        let funcres = func(children[i],index)
                        index++;
                        if(Array.isArray(funcres)){
                            let secKey= newkey+'/.'
                            console.log(secKey)
                            mapAll(funcres,secKey)
                        }else{//不返回数组会进来
                            let mapedkey = judgeKey(funcres,index,newkey+'/.')
                            funcres.key =mapedkey
                            res.push(funcres)
                        }
                    }        
                }
            }
          
        }
    }
    mapAll(children,'.')
    return res
}



export {
    mapChildren as map,
};
发布了178 篇原创文章 · 获赞 11 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/yehuozhili/article/details/105339404
今日推荐