程序员Web前端之路前端开发

走进 React 的内部

2019-06-02  本文已影响3人  zidea
react
今天我们来分析一下 React 的源码,以及在 React 中应用了那些 ES6 的新特性。
首先感谢 Nir Kaufman 大神的分享,最近看了他分享 Redux Pattern 受益匪浅。
nir kaufman
下面代码想必大家不会陌生,我们通过createElement方法来创建 create 元素,在 create 我们并没有找到 document 来对应于createElement来创建元素。
class App extends Component {
    render() {
        return (
           React.createElement('h1',null,"hello react")
        )
    }
}

ReactDom.render(
    React.createElement(App,null),
    document.getElementById('app')
)

通过ReactElement方法来创建一个 react 的 element 元素,通过分析ReactElement下面的源码,返回一个 element 对象,下面是其源码。

var ReactElement = function (type, key, ref, self, source, owner, props) {
  var 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
  };

  {
    // The validation flag is currently mutative. We put it on
    // an external backing store so that we can freeze the whole object.
    // This can be replaced with a WeakMap once they are implemented in
    // commonly used development environments.
    element._store = {};

    // To make comparing ReactElements easier for testing purposes, we make
    // the validation flag non-enumerable (where possible, which should
    // include every environment we run tests in), so the test framework
    // ignores it.
    Object.defineProperty(element._store, 'validated', {
      configurable: false,
      enumerable: false,
      writable: true,
      value: false
    });
    // self and source are DEV only properties.
    Object.defineProperty(element, '_self', {
      configurable: false,
      enumerable: false,
      writable: false,
      value: self
    });
    // Two elements created in two different places should be considered
    // equal for testing purposes and therefore we hide it from enumeration.
    Object.defineProperty(element, '_source', {
      configurable: false,
      enumerable: false,
      writable: false,
      value: source
    });
    if (Object.freeze) {
      Object.freeze(element.props);
      Object.freeze(element);
    }
  }

  return element;
};

通过 React.createElement 创建元素 el 后,我们尝试修改 el 的 myName 属性。

class App extends Component{
  render(){
    const el = React.createElement('h1',null,"hello react")
    el.myName = 'zidea';
    console.log(el);
    return el;    
    // return (
    //   <div>
    //     <Greeting/>
    //     <Greeting/>
    //   </div>
    // )
  }
}

export default App;

运行程序提示出现错误

TypeError: Cannot add property myName, object is not extensible

这是因为这个对象 el 是被冻结,我们无法对其添加属性或进行修改。可以通过Object.isFrozen方法可以坚持 el 是否被冻结。

    const el = React.createElement('h1',null,"hello react")
    // el.myName = 'zidea';
    console.log(Object.isFrozen(el));

返回 true 表示对象已经被冻结,冻结对象对象我们无法再为其添加新的属性。

true
const Title = ({title}) => React.createElement('h1',null,title)

class App extends Component{
  render(){
   
    return React.createElement(Title,{title:"hello"})
  }
}

export default App;
const Title = ({title}) => React.createElement('h1',null,title)

class App extends Component{
  constructor(props){
    super(props);
    this.state = {title:'zidea'}
  }

  render(){
    return React.createElement(Title,{title:this.state.title})
  }
}

尝试修改 props 同样可以可能会发生错误。

 if (Object.freeze) {
      Object.freeze(element.props);
      Object.freeze(element);
    }
class App extends Component{
  constructor(props){
    super(props);
    this.state = {title:'zidea'}
  }

  render(){
    return React.createElement('h1',null,this.state.title)
  }
}

freeze change name seal
通过上面演示我们可以查看,当 Object.freeze(), 我们无法通过 delete 来删除对象的属性,也无法修改冻结对象的属性值,而使用 seal 虽然无法删除属性,但却可以修改对象的属性。
// an immutable object with a single mutable value
function createRef() {
  var refObject = {
    current: null
  };
  {
    Object.seal(refObject);
  }
  return refObject;
}

看看 refObject 源码,这里通过Object.seal(refObject),因此我们可以修改 current 属性指向我们要引用的 DOM。

class App extends Component{
  constructor(props){
    super(props);
    this.titleRef = { current:{}};
    this.state = {title:'zidea'}
  }

  componentDidMount(){
    console.log(this.titleRef.current);
  }

  render(){
    return React.createElement('h1',{ref:this.titleRef},this.state.title)
  }
}

从上面代码我们不难看出 current 的属性是可以修改

var hasSymbol = typeof Symbol === 'function' && Symbol.for;

var REACT_ELEMENT_TYPE = hasSymbol ? Symbol.for('react.element') : 0xeac7;
var REACT_PORTAL_TYPE = hasSymbol ? Symbol.for('react.portal') : 0xeaca;
var REACT_FRAGMENT_TYPE = hasSymbol ? Symbol.for('react.fragment') : 0xeacb;
var REACT_STRICT_MODE_TYPE = hasSymbol ? Symbol.for('react.strict_mode') : 0xeacc;
var REACT_PROFILER_TYPE = hasSymbol ? Symbol.for('react.profiler') : 0xead2;
var REACT_PROVIDER_TYPE = hasSymbol ? Symbol.for('react.provider') : 0xeacd;
var REACT_CONTEXT_TYPE = hasSymbol ? Symbol.for('react.context') : 0xeace;

var REACT_CONCURRENT_MODE_TYPE = hasSymbol ? Symbol.for('react.concurrent_mode') : 0xeacf;
var REACT_FORWARD_REF_TYPE = hasSymbol ? Symbol.for('react.forward_ref') : 0xead0;
var REACT_SUSPENSE_TYPE = hasSymbol ? Symbol.for('react.suspense') : 0xead1;
var REACT_MEMO_TYPE = hasSymbol ? Symbol.for('react.memo') : 0xead3;
var REACT_LAZY_TYPE = hasSymbol ? Symbol.for('react.lazy') : 0xead4;

Symbol 可以创建一个唯一的 key 值,这个值是唯一的、可以通过一个实例来了解 Symbol 使用方法。我们可以通过 Symbol 来实现类似 java 中的接口。
上面语句不难看出我们通过判断是否支持 Symbol,如果支持就使用 Symbol 来作为类型的唯一标识,否则直接通过序列数来表示。

图1

定义 Tut 类,然后提供一个 print 方法,调用可使用 tut.print

图2

检查是否为 react 的元素。保护你的代码,其实并没有那么复杂。

$$typeof: REACT_ELEMENT_TYPE,
class App extends Component{
  constructor(props){
    super(props);
    this.titleRef = React.createRef();
    this.state = {title:'zidea'}
  }

  componentDidMount(){
    console.log(this.titleRef.current);
  }

  changetTitle(){
    this.setState({title:'tina'})
  }

  render(){
    return React.createElement('div',null,[
      React.createElement('h1',{key:1},this.state.title),
      React.createElement('button',{key:2, onClick:()=> this.changetTitle()},'click')
    ]
    )
  }
}

通过打印this.updater可以输出 this.updater 的方法,这里需要说一下更新 setState 方法是异步的,可以通过 enqueueSetState 方法可以更新 state 属性值。

  changetTitle(){
    console.log(this.updater);
  }
updater
  changetTitle(){
    this.updater.enqueueSetState(this,{title:'new title'});
  }
上一篇下一篇

猜你喜欢

热点阅读