zoukankan      html  css  js  c++  java
  • (转)React学习笔记(干货满满)

    1. React 长什么样

    React 是 facebook 开源出来的一套前端方案,官网在 https://reactjs.org 。

    先看一个简单的样子:

    <!DOCTYPE html>
    <html lang="zh-cmn-Hans">
    <head>
    <meta charset="utf-8" />
    <title>React</title>
    <script crossorigin src="https://s.zys.me/js/jq/jquery.min.js"></script>
    <script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
    </head>
    <body>
      <h1>Hello</h1>
      <div id="place"></div>
      <script type="text/javascript">
        var attribute = {
          className: 'text',
          style: {color: 'red'},
          id: 'me',
          href: 'http://' + 's.zys.me'
        };
        var inner = React.createElement('a', attribute, 'React');
        var instance = React.createElement('h1', null, '字符串在中间', inner);
        ReactDOM.render(instance, $('#place')[0]);
      </script>
    </body>
    </html>

    从上面可以看出,它的使用其实跟其它很多框架的方式差不多,即,找到一个 DOM 节点,然后在这个节点上做事,类似 jQuery 的 $(dom).xxx({}) 。

    在 React.createElement 中,第一个参数是标签名,第二个是 config ,其中包括了节点属性(class 因为关键词问题改用 className) ,第三个及以后,是子级节点,或者说子级元素。

    上面两个 createElement 得到的最终结果,是:

    <h1>
        字符串在中间
        <a class="text" style="color: red" id="me" href="http://s.zys.me">React</a>
    </h1>

    当然,这里的重点,不是 React.createElement ,而是它返回的那个 instance ,事实上,从源码 https://unpkg.com/react@16.2.0/umd/react.development.js 中看的话,可以看出它返回的是一个 ReactElement ,这个 ReactElement 就是整个 React 体系于众不同的地方,否则它直接返回一个 DOM Element 就好了。

    最后一行的 ReactDOM.render 就是处理 ReactElement 的, ReactDOM 的源码在 https://unpkg.com/react-dom@16.2.0/umd/react-dom.development.js , 这个东西自己实现了一套挂节点的树结构(所谓的 Virtual DOM),中间隔了一层再去处理真实的 DOM 渲染。这么做的好处是,因为自己有一套完整的树结构的,所以,可以在上面实现很多在原始 DOM 结构上不能做,或者不方便做的事。比如,异构渲染之类的。当然,局限的地方也很明显,本质上还是“节点”,其实没有一个往上的抽象,暴露的细节还是很多的,这种情况下,东西可以做得比较细,但是付出的成本也比较大,我是这样预测的。

    2. ReactElement 的定义

    最开始,已经简单演示了针对像 a h1 这类原生 DOM 元素的 ReactElement 定义, 自然,“能接受一个确定值的地方也应该可以接受一个函数”是 js 的一个惯例吧:

    <!DOCTYPE html>
    <html lang="zh-cmn-Hans">
    <head>
    <meta charset="utf-8" />
    <title>React</title>
    <script crossorigin src="https://s.zys.me/js/jq/jquery.min.js"></script>
    <script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
    </head>
    <body>
      <div></div>
      <script type="text/javascript">
        function Component(props) {
          if(props.tag === 'h1') {
            return React.createElement('h1', null, '大标题');
          } else {
            return React.createElement('span', null, '普通文本');
          }
        }
        var instance = React.createElement(Component, {tag: 'h1'});
        ReactDOM.render(instance, $('div')[0]);
      </script>
    </body>
    </html>

    上面代码的过程,大概是:

    • React.createElement(type, config, children) 返回 ReactElement() 。
    • ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props) ,上面 config 的一些东西,会补为这里的几个参数,及 props 里的东西。
    • ReactElement() 的调用会返回一个 element ,这是一个比较单纯的结构体,里面会有 typekey ref props _owner $$typeof 等内容。
    • ReactDOM.render(element, container, callback) 是对 renderSubtreeIntoContainer(parentComponent, children, container, forceHydrate, callback) 的调用, element 也转为 children 的统一概念了。
    • 然后是到 DOMRender (从 reactReconciler 来) 的 updateContainer(element, container, parentComponent, callback) ,最外层的调用, container 就是一个 newRoot ,前面的 children概念这里又变成 element 了。
    • 上面会处理一下 container ,接着到 scheduleTopLevelUpdate(current, element, callback) , current 是从 container 里取的。
    • scheduleTopLevelUpdate 是会作 scheduleWork(current, expirationTime) ,应该是处理一些同步的事。还有 构造一个 update 的结构体,然后 insertUpdateIntoFiber(current, update) , element被放到 update 中的 partialState: { element: element } 。
    • insertUpdateIntoFiber(fiber, update) , 新的 element 在 update 中,而 fiber 是 container的范畴。这里只是处理“两个队列”的状态,干活的还是在 scheduleWork() 中。
    • 这个地方开始, current 和 element 就分开了,current 继续进入 scheduleWork(fiber, expirationTime) 进行下一步处理。而 element 只在 insertUpdateIntoFiber 中更新于队列状态。
    • scheduleWork() 里实际上会用 scheduleWorkImpl(fiber, expirationTime, isErrorRecovery) ,这里开始迭代 fiber ,离真实的节点创建还有一半的路要走吧,中间又各种处理,最后 createInstance() 会创建真实的节点。

    反正,我从源码中找了一长串,还是没搞明白 createElement() 第一个参数的 type 可以是,应该是一个什么东西。

    从官方的文档来看 https://reactjs.org/docs/react-api.html#createelement ,这个 type 可以是三类东西:

    • 原生类节点,的标签字符串,比如 div , span 。
    • React component , React 组件。
    • React fragment ,这好像是 v16.2.0 加入的新机制,简单来说它使 React 可以支持直接渲染“一串”节点,而之前,只能直接渲染“一个”节点,如果是一串的需求,那么在外面需要包一个节点。我理解大概就是下面这个样子吧:
    <!DOCTYPE html>
    <html lang="zh-cmn-Hans">
    <head>
    <meta charset="utf-8" />
    <title>React</title>
    <script crossorigin src="https://s.zys.me/js/jq/jquery.min.js"></script>
    <script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
    </head>
    <body>
      <div></div>
      <script type="text/javascript">
        function Component(props) {
          return ['1', React.createElement('h1', null, props.text)];
        }
        var instance = React.createElement(Component, {text: '啊啊啊'});
        ReactDOM.render(instance, $('div')[0]);
      </script>
    </body>
    </html>

    React 组件有两种类型,一种是简单的“函数式组件”,另一种是复杂点的“类组件”。

    2.1. 函数式组件

    函数式组件就是接受 props ,然后返回一个 React element :

    function Component(props) {
      return React.createElement('h1', null, props.text);
    }

    2.2. 类组件

    类组件,是继承 React.Component ,然后重写一些方法:

    <!DOCTYPE html>
    <html lang="zh-cmn-Hans">
    <head>
    <meta charset="utf-8" />
    <title>React</title>
    <script crossorigin src="https://s.zys.me/js/jq/jquery.min.js"></script>
    <script crossorigin src="https://unpkg.com/react@16.2.0/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@16.2.0/umd/react-dom.development.js"></script>
    </head>
    <body>
      <div></div>
      <script type="text/javascript">
        class MyComponent extends React.Component {
          render(){
            return React.createElement('h1', null, this.props.text);
          }
        }
        var instance = React.createElement(MyComponent, {text: '哈哈'});
        ReactDOM.render(instance, $('div')[0]);
      </script>
    </body>
    </html>

    3. createElement 的 DSL 方案 JSX

    前面提过,如果你想通过 createElement 构造一个类似:

    <h1>
        字符串在中间
        <a class="text" style="color: red" id="me" href="http://s.zys.me">React</a>
    </h1>

    那么你需要的 js 大概是:

    var attribute = {
      className: 'text',
      style: {color: 'red'},
      id: 'me',
      href: 'http://' + 's.zys.me'
    };
    var inner = React.createElement('a', attribute, 'React');
    var instance = React.createElement('h1', null, '字符串在中间', inner);
    ReactDOM.render(instance, $('#place')[0]);

    看起来是比较麻烦的,于 React 中引入了一个新的东西,JSX ,即 JavaScript XML ,简单来说,就是 javascript 和 XML 的混写,上面的代码可以写成:

    var inner = <a className="text" style={ {color: 'red'} } id="me" href={'http://' + 's.zys.me'}>React</a>;
    var instance = <h1>字符串在中间 {inner}</h1>;
    ReactDOM.render(instance, $('#place')[0]);

    甚至是:

    var inner = <a className="text" style={ {color: 'red'} } id="me" href={'http://' + 's.zys.me'}>React</a>;
    ReactDOM.render(<h1>字符串在中间 {inner}</h1>, $('#place')[0]);

    可以看出,比原来直接写 createElement 的方式要简洁得多, JSX 除了支持直接写 XML 风格的标签,还可以使用 {} 处理表达式。不过, JSX 并不是浏览器的标准,要让它跑在浏览器,需要在发布前专门编译到纯 js ,平时测试,也可以直接用 babel 来跑:

    <!DOCTYPE html>
    <html lang="zh-cmn-Hans">
    <head>
    <meta charset="utf-8" />
    <title>React</title>
    <script crossorigin src="https://s.zys.me/js/jq/jquery.min.js"></script>
    <script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
    <script crossorigin src="https://unpkg.com/babel-standalone@6.15.0/babel.js"></script>
    </head>
    <body>
      <h1>Hello</h1>
      <div id="place"></div>
      <script type="text/babel">
        var inner = <a className="text" style={ {color: 'red'} } id="me" href={'http://' + 's.zys.me'}>React</a>;
        ReactDOM.render(<h1>字符串在中间 {inner}</h1>, $('#place')[0]);
      </script>
    </body>
    </html>

    注意,上在的 script 的 type 写的是 text/babel 哦。

    babel 也有命令行的工具,先安装:

    npm install -g babel
    npm install -g babel-cli
    npm install -g babel-preset-react

    然后对于一个 demo.jsx :

    var inner = <a className="text" style={ {color: 'red'} } id="me" href={'http://' + 's.zys.me'}>React</a>;
    ReactDOM.render(<h1>字符串在中间 {inner}</h1>, $('#place')[0]);

    可以使用 babel 编译它:

    babel --presets=react demo.jsx

    就可以看到标准的 js 输出了:

    var inner = React.createElement(
      "a",
      { className: "text", style: { color: 'red' }, id: "me", href: 'http://' + 's.zys.me' },
      "React"
    );
    ReactDOM.render(React.createElement(
      "h1",
      null,
      "u5B57u7B26u4E32u5728u4E2Du95F4 ",
      inner
    ), $('#place')[0]);

    babel 默认输出的到标准输出,可以通过 -o 输出到指定文件。

    平时的前端项目,一般把 babel 和 webpack 结合使用,在构建过程处理 JSX 格式。

    3.1. JSX 中的名字

    JSX 中的名字,如果是自定义组件,名字开头需要大写,小写的是 DOM 标签。

    名字中,可以带名字空间:

    <MyComponent.A.B />

    也可以是变量值:

    let My = [MyComponent][0];
    return <My />

    但是,不能带表达式(下面是错误的):

    <[MyComponent][0] />

    提示一下, JSX 不是模板,它的行为,更像是“宏”。本质,还是 js 代码。 宏是没有运行时能力的,自然无法求值表达式。

    4. 使用 state 处理数据单向绑定

    前面已经提到过 props 了, props 一般处理初始化后就不改变的数据,对于一个组件中要变化的数据,需要放在 state 中处理,因为 React 专门设计了组件的 setState() 来维护 state (不要直接更改 state ,而是使用 setState()) ,同时 setState() 还处理节点渲染相关的事,通过函数调用,来实现“数据到展现”的单向绑定。

    <!DOCTYPE html>
    <html lang="zh-cmn-Hans">
    <head>
    <meta charset="utf-8" />
    <title>React</title>
    <script crossorigin src="https://s.zys.me/js/jq/jquery.min.js"></script>
    <script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
    <script crossorigin src="https://unpkg.com/babel-standalone@6.15.0/babel.js"></script>
    </head>
    <body>
      <div id="place"></div>
      <script type="text/babel">
    
        class MyComponent extends React.Component {
          constructor(props) {
              super(props);
              this.state = {date: (new Date()).valueOf()};
              setInterval(() => this.setState({date: (new Date()).valueOf()}), 1000);
          }
    
          render(){
            return <h1>Hello {this.props.name} {this.state.date}</h1>;
          }
    
        }
    
        ReactDOM.render(<MyComponent name="哈哈" />, $('#place')[0]);
      </script>
    </body>
    </html>

    4.1. setState

    setState() 也可以接受一个函数,这个函数的参数第一个是当前的 state ,另一个是 props :

    class MyComponent extends React.Component {
      constructor(props) {
          super(props);
          this.state = {date: (new Date()).valueOf()};
          setInterval(() => this.setState((prevState, props) => ({date: prevState.date + 1000})), 1000);
      }
    
      render(){
        return <h1>Hello {this.props.name} {this.state.date}</h1>;
      }
    
    }
    
    ReactDOM.render(<MyComponent name="哈哈" />, $('#place')[0]);

    5. 事件处理

    React 自己维护了一个节点状态的树结构,并且,同时也自己实现了一套事件机制,只是这套事件,跟浏览器自身的区别不是很大:

    class MyComponent extends React.Component {
      constructor(props) {
          super(props);
          this.state = {date: (new Date()).valueOf()};
          setInterval(() => this.setState((prevState, props) => ({date: prevState.date + 1000})), 1000);
      }
    
      handleClick = () => {
          console.log(this);
      }
    
      render(){
          return <h1 onClick={this.handleClick}>Hello {this.props.name} {this.state.date}</h1>;
      }
    
    }
    
    ReactDOM.render(<MyComponent name="哈哈" />, $('#place')[0]);

    注意那个 handleClick 的写法:

    handleClick = () => {
        console.log(this);
    }

    5.1. this 的问题

    如果不用“箭头函数”把 this 固定住了,那么在使用时就需要显式绑定:

    handlerClick(){
        console.log(this);
    }
    
    render(){
        return <h1 onClick={this.handleClick.bind(this)}>Hello {this.props.name} {this.state.date}</h1>;
    }

    React 自己的事件,是“驼峰式”的名字,比如 onClick ,而 onclick 则是浏览器自己的事件了。

    其它支持的完整的事件列表,见: https://reactjs.org/docs/events.html

    6. 列表与 key

    <!DOCTYPE html>
    <html lang="zh-cmn-Hans">
    <head>
    <meta charset="utf-8" />
    <title>React</title>
    <script crossorigin src="https://s.zys.me/js/jq/jquery.min.js"></script>
    <script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
    <script crossorigin src="https://unpkg.com/babel-standalone@6.15.0/babel.js"></script>
    </head>
    <body>
      <div id="place"></div>
      <script type="text/babel">
    
        class MyComponent extends React.Component {
          constructor(props) {
              super(props);
              this.state = {itemList: [{name: 'abc', id: 'a'}, {name: 'oiii', id: 'b'}] };
          }
    
          handleClick(o){
            return (e) => {
              console.log(o);
            }
          }
    
          render(){
            return <div>
              {this.state.itemList.map( (o) => (<div key={o.id} onClick={this.handleClick(o)}>{o.name}</div>) ) }
              </div>
          }
    
        }
    
        ReactDOM.render(<MyComponent name="哈哈" />, $('#place')[0]);
      </script>
    </body>
    </html>

    对于列表的渲染, React 要求节点上需要添加 key 属性,否则,会报 warn (是的,不会报错)。如果没有明确的 id 之类的标识,可以使用序列的索引值 index 。这个 key 属性的作用, React 会用于标识节点,以便在数据变更时,对应地可以追踪到节点的变更情况。

    7. 组件的各状态 Hook (生命周期)

    对于一个 React 组件,准备说,“类组件”,它在渲染及销毁过程中,每个状态转换时, React 都预留了相关的 Hook 函数,这些函数,一共 10 个,可以分成 4 类:

    • 渲染时
      • constructor(props)
      • componentWillMount()
      • render()
      • componentDidMount() ,从这里开始, setState() 会触发重新的 render() 。
    • 修改时
      • componentWillReceiveProps(nextProps) , props 改变时触发,可以由于父组件的变化引起。 setState() 一般不会触发这个过程。
      • shouldComponentUpdate(nextProps, nextState) ,如果返回 false ,则使 React “尽量” 不要重新渲染组件,注意,只是 “尽量” 而已。如果渲染上有性能问题,则应该考虑其它解决方案。
      • componentWillUpdate(nextProps, nextState) ,上面的 shouldComponentUpdate() 如果返回 false ,则这个过程一定不会触发,现时,第一次初始化不会触发这个过程。
      • render()
      • componentDidUpdate(prevProps, prevState) ,第一次初始化不会触发这个过程。
    • 销毁时
      • componentWillUnmount()
    • 出错时
      • componentDidCatch(error, info)

    这几个方法的一些细节:

    componentWillReceiveProps

    • 初始化时不会被调用。
    • 可以在这里作 setState() 。
    • props 没变化时也可能被调用。

    componentWillUpdate

    • 初始化时不会被调用。
    • 不能在这里作 setState() , 否则会再次触发变化流程,造成死循环。
    • shouldComponentUpdate() 为 false 时不会调用。

    componentDidUpdate

    • 初始化时不会被调用。
    • 远程请求常放在这里,因为可以先判断 props 的相应变化后再决定是否作新的远程调用。

    除了上面讲的过程 Hook 函数,及前面用过的 setState() ,组件还有一个 forceUpdate(callback)方法,它的调用可以触发 render() ,并且跳过 shouldComponentUpdate() 。

    另外,在“类”层面,有两个属性, defaultProps 用于定义默认的 props , displayName ,用于定义调试时显示的名字。

    试试各个过程的表现吧:

    <!DOCTYPE html>
    <html lang="zh-cmn-Hans">
    <head>
    <meta charset="utf-8" />
    <title>React</title>
    <script crossorigin src="https://s.zys.me/js/jq/jquery.min.js"></script>
    <script crossorigin src="https://s.zys.me/js/react/react.min.js"></script>
    <script crossorigin src="https://s.zys.me/js/react/react-dom.min.js"></script>
    <script crossorigin src="https://s.zys.me/js/react/babel.min.js"></script>
    </head>
    <body>
      <div></div>
      <script type="text/babel">
        class MyComponent extends React.Component {
          constructor(props){
            super(props);
            this.state = {name: '初始化'};
            let that = this;
            setTimeout(function(){
              console.log('2s go ...');
              that.setState({name: '修改了的'});
            }, 2000);
            console.log('constructor');
          }
    
          componentWillMount(){
            console.log('componentWillMount');
          }
    
          componentDidMount(){
            console.log('componentDidMount');
          }
    
          componentWillReceiveProps(nextProps){
            console.log('componentWillReceiveProps');
          }
    
          shouldComponentUpdate(nextProps, nextState){
            console.log('shouldComponentUpdate');
            return true;
          }
    
          componentWillUpdate(nextProps, nextSate){
            console.log('componentWillUpdate');
          }
    
          componentDidUpdate(prevProps, prevState){
            console.log('componentDidUpdate');
          }
    
          componentWillUnmount(){
            console.log('componentWillUnmount');
          }
    
          componentDidCatch(error, info){
            console.log('componentDidCatch');
          }
    
          render(){
            console.log('render');
            if(this.props.error){throw Error()};
            return <h1>{this.state.name}</h1>;
          }
    
        }
        ReactDOM.render(<MyComponent />, $('div')[0]);
    
        setTimeout(function(){
          console.log('5s go ...');
          ReactDOM.render(<MyComponent />, $('div')[0]);
        }, 5000);
    
        setTimeout(function(){
          console.log('10s go ...');
          ReactDOM.render(null, $('div')[0]);
        }, 10000);
    
      </script>
    </body>
    </html>

    上面的代码会输出:

    constructor
    componentWillMount
    render
    componentDidMount
    2s go ...
    shouldComponentUpdate
    componentWillUpdate
    render
    componentDidUpdate
    5s go ...
    componentWillReceiveProps
    shouldComponentUpdate
    componentWillUpdate
    render
    componentDidUpdate
    10s go ...
    componentWillUnmount

    上面过程看起来都很好理解的。

    少的那个 componentDidCatch 是用来抓子组件错误的:

    <!DOCTYPE html>
    <html lang="zh-cmn-Hans">
    <head>
    <meta charset="utf-8" />
    <title>React</title>
    <script crossorigin src="https://s.zys.me/js/jq/jquery.min.js"></script>
    <script crossorigin src="https://s.zys.me/js/react/react.min.js"></script>
    <script crossorigin src="https://s.zys.me/js/react/react-dom.min.js"></script>
    <script crossorigin src="https://s.zys.me/js/react/babel.min.js"></script>
    </head>
    <body>
      <div></div>
      <script type="text/babel">
        class ErrorWrapper extends React.Component {
          constructor(props){
            super(props);
            this.state = {error: null};
          }
    
          componentDidCatch(error, info){
            console.log('componentDidCatch');
            console.log(error, info);
            this.setState({error: {error: error, info: info}});
          }
    
          render(){
            if(!!this.state.error) {
              return <h1>Error Here</h1>
            } else {
              return this.props.children || '';
            }
          }
        }
    
        class MyComponent extends React.Component {
          render(){
            throw Error();
          }
        }
    
        ReactDOM.render(<ErrorWrapper><MyComponent /></ErrorWrapper>, $('div')[0]);
    
      </script>
    </body>
    </html>

    上面代码占, MyComponent 的错误,会触发 ErrorWrapper 的 componentDidCatch ,参数中的 error 是一串调用栈字符串, info 是一个结构,里面有一个 componentStack 字符串记录了组件序列。

    8. 外围函数

    React 对于 Component 的处理,前面介绍得差不多了,这里说的外围函数,是指还没有脱离 DOM 的那几个工具,比如前面用到的 ReactDOM.render(child, container) 就是一个典型。

    • ReactDOM.unmountComponentAtNode(container) , 从一个节点中删除组件。
    • ReactDOM.findDOMNode(component) ,找到组件“渲染的内容”所在的 DOM 节点,注意,不是组件自己所在的节点。
    • ReactDOM.createPortal(child, container) ,在 render() 中,取代返回 ReactElement ,而在一个确认的位置渲染出组件。(比如“弹层”的那种情况)

    前 2 个方法是比较好理解的:

    class MyComponent extends React.Component {
      render(){
        return <h1 onClick={this.clickHandler}>哈哈哈</h1>;
      }
    
      clickHandler = () => {
        var dom = ReactDOM.findDOMNode(this);
        console.log(dom);
        ReactDOM.unmountComponentAtNode($('div')[0]);
      }
    }
    
    ReactDOM.render(<MyComponent />, $('div')[0]);

    8.1. ReactDOM.createPortal

    ReactDOM.createPortal(child, container) 一般用在需要自己创建并维护真实 DOM 节点的地方:

    <!DOCTYPE html>
    <html lang="zh-cmn-Hans">
    <head>
    <meta charset="utf-8" />
    <title>React</title>
    <script crossorigin src="https://s.zys.me/js/jq/jquery.min.js"></script>
    <script crossorigin src="https://s.zys.me/js/react/react.min.js"></script>
    <script crossorigin src="https://s.zys.me/js/react/react-dom.min.js"></script>
    <script crossorigin src="https://s.zys.me/js/react/babel.min.js"></script>
    </head>
    <body>
      <div></div>
      <script type="text/babel">
    
        class MyComponent extends React.Component {
          constructor(props){
            super(props);
            this.dom = document.createElement('div');
          }
    
          componentDidMount(){
            $('body').append($(this.dom));
          }
    
          componentWillUnmount(){
            $(this.dom).remove();
          }
    
          clickHandler = () => {
            var dom = ReactDOM.findDOMNode(this);
            console.log(dom);
            ReactDOM.unmountComponentAtNode($('div')[0]);
          }
          
          render(){
            return ReactDOM.createPortal(<div onClick={this.clickHandler}>{this.props.children}</div>, this.dom);
          }
    
        }
    
        ReactDOM.render(<MyComponent><h1>弹出来的东西</h1></MyComponent>, $('div')[0]);
    
      </script>
    </body>
    </html>

    上面的代码演示了 ReactDOM.createPortal 的使用,需要注意的是,即使一个组件的 render() 实现,不是 ReactElement 而是 ReactDOM.createPortal ,组件本身,还是需要依附于父组件,或者 ReactDOM.render() 的,即坑位在占住,这种时候,就有点“它在那里,它又不在那里”的感觉。

    9. DOM 元素自己的那些东西

    React 的实现的基础是自己实现的一套“树状节点结构”,然后再把这个结构放到浏览器中用真实的 DOM 展现出来。那么这中间,自己搞的一套,与浏览器环境下的一套,之间肯定会有少许的差异的,虽然 React 连事件这种都自己实现了一遍。

    首先,对于节点的属性来说, React 的属性都是“驼峰式”的名字,与 DOM 自己的一些全小写属性名不同,比如, onclick 变成了 onClick , tabindex 变成了 tabIndex , SVG 中的属性 React 也支持。

    属性名中的 class ,因为关键词因素,在 React 中变成了 className 。

    style 在 React 中作了扩展实现的,给它一个对象值的话,可以得到正确的样式。

    onChange 事件在 React 中的实现也作了增强,可以实时触发相应的回调。

    label 标签的 for 属性,在 React 中可以使用 htmlFor 代替。

    9.1. dangerouslySetInnerHTML

    dangerouslySetInnerHTML 是一个特殊的属性,可以用于直接添加原始 HTML 内容:

    <!DOCTYPE html>
    <html lang="zh-cmn-Hans">
    <head>
    <meta charset="utf-8" />
    <title>React</title>
    <script crossorigin src="https://s.zys.me/js/jq/jquery.min.js"></script>
    <script crossorigin src="https://s.zys.me/js/react/react.min.js"></script>
    <script crossorigin src="https://s.zys.me/js/react/react-dom.min.js"></script>
    <script crossorigin src="https://s.zys.me/js/react/babel.min.js"></script>
    </head>
    <body>
      <div></div>
      <script type="text/babel">
    
        class MyComponent extends React.Component {
          getHtml = () => {
            return {__html: '<h1 style="color: red">哈哈哈</h1>'};
          }
          
          render(){
            return <div dangerouslySetInnerHTML={this.getHtml()} />
          }
    
        }
    
        ReactDOM.render(<MyComponent />, $('div')[0]);
    
      </script>
    </body>
    </html>

    9.2. ref

    ref 也是 React 中的一个特殊属性,用于回调真实 DOM 节点的引用:

    <!DOCTYPE html>
    <html lang="zh-cmn-Hans">
    <head>
    <meta charset="utf-8" />
    <title>React</title>
    <script crossorigin src="https://s.zys.me/js/jq/jquery.min.js"></script>
    <script crossorigin src="https://s.zys.me/js/react/react.min.js"></script>
    <script crossorigin src="https://s.zys.me/js/react/react-dom.min.js"></script>
    <script crossorigin src="https://s.zys.me/js/react/babel.min.js"></script>
    </head>
    <body>
      <div></div>
      <script type="text/babel">
    
        class MyComponent extends React.Component {
          clickHandler = () => {
            console.log(this.h1);
          }
    
          render(){
            return <h1 ref={(node) => {this.h1 = node}} onClick={this.clickHandler}>哈哈</h1>
          }
    
        }
    
        ReactDOM.render(<MyComponent />, $('div')[0]);
    
      </script>
    </body>
    </html>

    ref 可以用于控制焦点,或者在节点上使用浏览器的其它一些 API ,比如音频,视频等。

    10. 测试工具

    React 因为有自己的一套树状态,所以,理论上它在测试方面有着得天独厚的优势,因为即使是在浏览器环境,不涉及 DOM 相关 API 的行为,只需要在那个树状态中的去操作,并检查操作后的状态就可以达到测试目的。当然,我们也不用关心 React 的一些实现细节,现在说测试,会有两方面的内容。一方面是 React 提供一些测试相关的工具方法,只是工具方法,如果你要建立完整的测试体系,还需要自己解决断言库,测试用例组件与调度等问题。另一方面,是看看非浏览器的渲染,如果可以完全摆脱浏览器环境,完成大部分的测试工作,那将是很美好的一件事。

    11. 测试 API

    <!DOCTYPE html>
    <html lang="zh-cmn-Hans">
    <head>
    <meta charset="utf-8" />
    <title>React</title>
    <script crossorigin src="https://s.zys.me/js/jq/jquery.min.js"></script>
    <script crossorigin src="https://s.zys.me/js/react/react.min.js"></script>
    <script crossorigin src="https://s.zys.me/js/react/react-dom.min.js"></script>
    <script crossorigin src="https://s.zys.me/js/react/babel.min.js"></script>
    <script crossorigin src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.2.0/umd/react-dom-test-utils.development.js"></script>
    </head>
    <body>
      <div></div>
      <script type="text/babel">
    
        class MyComponent extends React.Component {
          constructor(props){
            super(props);
            this.state = {n: 0};
          }
    
          clickHandler = () => {
            this.setState((s) => ({n: s.n + 1}));
          }
    
          render(){
            return <h1 onClick={this.clickHandler}>{ this.state.n }</h1>
          }
    
        }
    
        let component = ReactTestUtils.renderIntoDocument(<MyComponent />);
        console.log(component);
        let node = ReactDOM.findDOMNode(component)
        console.log(component.state);
        ReactTestUtils.Simulate.click(node);
        console.log(component.state);
    
        console.log(ReactTestUtils.isElement(<MyComponent />));
        console.log(ReactTestUtils.isElementOfType(<MyComponent />, MyComponent));
        console.log(ReactTestUtils.isDOMComponent(ReactTestUtils.renderIntoDocument(<h1>xx</h1>)));
        console.log(ReactTestUtils.isCompositeComponent(component));
        console.log(ReactTestUtils.isCompositeComponentWithType(component, MyComponent));
        console.log(ReactTestUtils.findRenderedDOMComponentWithTag(component, 'h1'));
        console.log(ReactTestUtils.findRenderedComponentWithType(component, MyComponent));
    
      </script>
    </body>
    </html>

    引入额外的资源文件,相关的方法在浏览器是完全可以用的。

    不过,完成的测试体系的话,还需要搭配其它工具来完成, React 的相关 API 只是提供了获取信息的方法。

    11.1. 非浏览器渲染

    在 nodejs 环境,可以使用 react-test-renderer 来完成对相关组件的处理,并按树结构遍历结果:

    const React = require('react');
    const TestRenderer = require('react-test-renderer');
    
    class MyComponent extends React.Component {
      constructor(props){
        super(props);
        this.state = {number: 0};
      }
      render(){
        return <h1 onClick={ () => {this.setState((s) => ({number: s.number + 1}))} }>{this.state.number}</h1>;
      }
    }
    
    
    const testRenderer = TestRenderer.create(<MyComponent />);
    testRenderer.root.findByType('h1').props.onClick();
    console.log(testRenderer.root.findByType('h1').children);
    console.log(testRenderer.root.instance.state);

    这部分完整的 API 在 https://reactjs.org/docs/test-renderer.html 。

    在非浏览器环境下,“模拟点击”这种操作是没有意义的,但是 onClick 本身并没有说一定是“点击”,它只是指明了一个回调函数而已,浏览环境下是点击触发,那么纯编程环境下,直接调用即可。

    12. Redux

    12.1. 基本概念

    Redux 并不是 React 必不可少的一部分,它只是随着 React 出来的一套模式,并且带了一个这个模式的实现: https://redux.js.org/ 。 简单来说,这个模式是着眼于“组件状态及组件间通信”的,换句话,它定义了一种“相对独立的组件句柄”。

    <!DOCTYPE html>
    <html lang="zh-cmn-Hans">
    <head>
    <meta charset="utf-8" />
    <title>Redux</title>
    <script src="https://unpkg.com/redux@latest/dist/redux.min.js"></script>
    </head>
    <body>
    
      <script type="text/javascript">
        var store = Redux.createStore(function reducer(state, action){
          console.log('here', state);
          if(action.type === 'HAHA'){ return 'OK' }
          return 'ERROR';
        });
        console.log(store.getState());
        store.subscribe(function render(){
          console.log('subscribe');
          console.log(store.getState());
        })
        store.dispatch({type: 'HAHA'});
      </script>
    
    </body>
    </html>

    Redux 的流程本身非常简单,如上所示,通过一个 reducer 初始化一个 store , 这个 store 先 subscribe 一些动作,然后使用时在需要的时候 dispatch 出一个 action 就好了。 dispatch 的对应逻辑是 reducer 中的事,而 reducer 执行完就轮到 subscribe 的回调了,就是这样的一个执行循环。

    12.2. React-Redux

    React-Redux 是 Redux 在 React 上的一个实现,直观点说,就是结合了 React 的 Component 机制的一套 Redux 流程。它的核心点,我个人的理解是下面几点:

    • 首先,组件本身进行两分类,一类“纯组件”,一类“通信组件”。“纯组件”的意思,就是参入 props 之后,它自己就可以完全独立工作的,里面尽量不包含业务逻辑。而“通信组件”的作用,就是在“纯组件”之上,加入具体业务逻辑,去完成各“纯组件”的调度。
    • 在实现“通信组件”上,并不推荐你去手写,而是根据 Redux 的思路, React-Redux提供相应用的包装 API ,来完成 State -> Props 和 Dispatch -> Props 的这个过程。
    • 进一步说,“纯组件”的行为,完全由 props 控制,无论是相应的数据输入,还是一些事件回调的输出。状态性的一些东西,则由上层的“通信组件”,通过 state 和 dispatch 来维护,上层通信组件的状态性的数据,会更新到“纯组件”的 props 上,来完成页面重渲染。
    • 本质上,是实现上的一种垂直抽象分层, Redux 的具体 API 不是重点。

    具体代码:

    <!DOCTYPE html>
    <html lang="zh-cmn-Hans">
    <head>
    <meta charset="utf-8" />
    <title>React</title>
    <script crossorigin src="https://s.zys.me/js/jq/jquery.min.js"></script>
    <script crossorigin src="https://s.zys.me/js/react/react.min.js"></script>
    <script crossorigin src="https://s.zys.me/js/react/react-dom.min.js"></script>
    <script crossorigin src="https://s.zys.me/js/react/babel.min.js"></script>
    <script crossorigin src="https://s.zys.me/js/react/redux.min.js"></script>
    <script crossorigin src="https://s.zys.me/js/react/react-redux.min.js"></script>
    </head>
    <body>
      <div id="app"></div>
      <script type="text/babel">
    
        const Title = props => (
          <h1 onClick={props.onClick}>{props.name}</h1>
        )
    
        const mapStateToProps = state => {
          return {...state};
        }
    
        const mapDispatchToProps = dispatch => {
          return {
            onClick: id => {
              dispatch({type: "CLICK"});
            }
          }
        }
    
        const VisibleTitle = ReactRedux.connect( mapStateToProps, mapDispatchToProps )(Title);
    
        let store = Redux.createStore( (state, action) => {
          if(action.type === 'CLICK'){
            return {name: '点击之后的'};
          }
          return {name: '初始化的'};
        });
    
        const App = () => (
          <ReactRedux.Provider store={store}>
            <VisibleTitle />
          </ReactRedux.Provider>
        )
    
        ReactDOM.render(<App />, document.getElementById('app'));
      </script>
    </body>
    </html>

    上面的组件实现的效果很简单,就是点击之后,改变文本。但是经过 React-Redux 分拆之后,感觉复杂了好多。不过,直观地也可以看出,分拆之后, Title 这个组件变“纯”了,它的行为一目了解,没有什么“点击之后,改变文本”这类逻辑,只有单纯的接受输入,然后渲染组件。操作上的逻辑,则由外层的 VisibleTitle 来完成。

    VisibleTitle 通过它的 state 状态性数据,来控制下面的 Title 的渲染。 state 则是通过 Redux 的 store 来承接的。进一步, state 到 Title 的 props ,可能只是 Title 需要输入的一部分(数据部分),另一部分涉及“反馈”到 state 变化的 props ,则由 Redux 的 dispatch - action 来承接。

    说的这两部分,也就对应着 mapStateToProps 和 mapDispatchToProps ,一个处理数据,一个调度 dispatch 的执行。然后使用 connect 这个现成的方法生成包裹在 Title 之上的 VisibleTitle 。

    当然,事情到这里还没有结束,再上层,还有一个 store , Provider 是 React-Redux 提供的东西,目的是给里面的组件提供 store 。而得到 store ,又需要 Reducer , dispatch 出的 action ,及 state 的变化实际上是在这里处理。

    12.3. Redux 中间件

    Redux 的设计中,所有的状态变化是通过 dispatch 这一个点完成的(并且这个 API 又非常简单),自然,围绕这一个点,就可以很容易完成“套圈”。

    let next = store.dispatch;
    store.dispatch = function(action){
        console.log(action);
        next(action);
    }

    这都完全不需要特别设计,你自己就可以随便搞。

    不过从 Monkeypatching 作为切入,官方的 https://redux.js.org/docs/advanced/Middleware.html 这份文档写得非常好,循序渐进,推荐一读。

    最后,官方 API 在这上面的支持,或者说推荐作法,是通过高阶函数定义 Middleware ,然后使用 applyMiddleware 挂到 store 里面去。

    const logger = store => next => action => {
      console.log('dispatching', action, store.getState());
      let result = next(action);
      console.log('next state', store.getState());
      return result
    }

    这是定义,使用时:

    import { createStore, combineReducers, applyMiddleware } from 'redux'
    let store = createStore( reducer, applyMiddleware(logger) )

    用前面的简单例子可以试试:

    function logger(store) {
      return function(next){
        return function(action){
          console.log('dispatching', action, store.getState());
          var result = next(action);
          console.log('next state', store.getState());
          return result
        }
      }
    }
    
    var store = Redux.createStore(function reducer(state, action){
      if(action.type === 'HAHA'){ return 'OK' }
      return 'ERROR';
    }, Redux.applyMiddleware(logger));
    
    store.subscribe(function render(){
      console.log('sub', store.getState());
    })
    store.dispatch({type: 'HAHA'});

    12.4. 异步流程与 redux-promise

    了解了中间件,再来看异步流程,就没什么新问题了。

    在 Redux 中,state 的维护是在 reducer 中的,从前面的例子可以看出, reducer 本身的设计,是没有考虑异步情况的,它的结构: (state, action) => newState 是同步的。

    如果我们在某个 dispatch 之后,需要异步获取数据(事实上这很常见),并把这个东西更新到 store 中去,我们就需要额外做点事,比较很容易想到的一个办法:

    <!DOCTYPE html>
    <html lang="zh-cmn-Hans">
    <head>
    <meta charset="utf-8" />
    <title>Redux</title>
    <script src="https://unpkg.com/redux@latest/dist/redux.min.js"></script>
    </head>
    <body>
    
      <script type="text/javascript">
        var store = Redux.createStore(function reducer(state, action){
          if(action.type === 'HAHA'){
            setTimeout(function(){
              store.dispatch({type: 'FETCH', payload: '123'});
            }, 3000);
            return 'LOADING';
          }
          if(action.type === 'FETCH'){
            return action.payload;
          }
          return 'ERROR';
        });
        store.subscribe(function render(){
          console.log('sub', store.getState());
        })
        store.dispatch({type: 'HAHA'});
      </script>
    
    </body>
    </html>

    简单来说,在 dispatch 之后,我们异步发出请求,并在当前至少有对 dispatch 的引用,然后返回一个中间状态的 state ,甚至是没有任何更改的 state 。在异步响应之后,再次 dispatch ,同时把异步响应的数据放到 action 中传递出去。

    这种方式,虽然绕了一点,但是本身没毛病,不过要多定义出来一个 dispatch 类型,是很烦人就是了。

    考虑前面讲过的“中间件”机制,我们很容易对 dispatch 的行为作一些扩展,加入对异步的支持。

    先看“中间件”的样子:

    const logger = store => next => action => {
      console.log('dispatching', action, store.getState());
      let result = next(action);
      console.log('next state', store.getState());
      return result
    }

    很显然,对于 dispatch 传入的 action 这个东西,我们可以先判断它的特征,或者说状态,根据既定规则决定在什么时候作 next(action) 。

    事实上, redux-promise 的代码就几行:

    import { isFSA } from 'flux-standard-action';
    
    function isPromise(val) {
      return val && typeof val.then === 'function';
    }
    
    export default function promiseMiddleware({ dispatch }) {
      return next => action => {
        if (!isFSA(action)) {
          return isPromise(action)
            ? action.then(dispatch)
            : next(action);
        }
    
        return isPromise(action.payload)
          ? action.payload.then(
              result => dispatch({ ...action, payload: result }),
              error => {
                dispatch({ ...action, payload: error, error: true });
                return Promise.reject(error);
              }
            )
          : next(action);
      };
    }

    做的事,就是当 dispatch 出去的东西,里面有一个 then 成员,并且这个成员是一个函数的话,那么 reducer 会返回这个函数的调用结果,并且调用这个函数时传入的是当前的 dispatch 。

    简单演示一下大概是:

    <!DOCTYPE html>
    <html lang="zh-cmn-Hans">
    <head>
    <meta charset="utf-8" />
    <title>Redux</title>
    <script src="https://unpkg.com/redux@latest/dist/redux.min.js"></script>
    </head>
    <body>
    
      <script type="text/javascript">
        var store = Redux.createStore(function reducer(state, action){
          if(!!action.then){
            return action.then(store.dispatch.bind(store));
          }
          return 'GOT: ' + action.payload;
        });
        store.subscribe(function render(){
          console.log('sub', store.getState());
        })
    
        store.dispatch({type: '123', payload: 'null'});
        setTimeout(function(){
          store.dispatch({type: 'async', then: function(dispatch){
            setTimeout(function(){
              dispatch({type: 'xxx', payload: 'async data'});
            }, 3000);
          }});
        }, 2000);
    
      </script>
    
    </body>
    </html>

    12.5. Redux 流程中的重复计算问题

    前面一个例中,有一个 mapStateToProps ,把例子改一点点看看:

    <!DOCTYPE html>
    <html lang="zh-cmn-Hans">
    <head>
    <meta charset="utf-8" />
    <title>React</title>
    <script crossorigin src="https://s.zys.me/js/jq/jquery.min.js"></script>
    <script crossorigin src="https://s.zys.me/js/react/react.min.js"></script>
    <script crossorigin src="https://s.zys.me/js/react/react-dom.min.js"></script>
    <script crossorigin src="https://s.zys.me/js/react/babel.min.js"></script>
    <script crossorigin src="https://s.zys.me/js/react/redux.min.js"></script>
    <script crossorigin src="https://s.zys.me/js/react/react-redux.min.js"></script>
    </head>
    <body>
      <div id="app"></div>
      <script type="text/babel">
    
        const getName = (name, count) => {
          console.log('getName');
          return name + count;
        }
    
        const Title = props => (
          <div>
            <h1 onClick={props.onClick}>{props.name}</h1>
            <h1 onClick={props.onOtherClick}>另外的 {props.name}</h1>
          </div>
        )
    
        const mapStateToProps = (state, props) => {
          console.log('mapStateToProps');
          return {
            name: getName(state.name, state.count)
          }
        }
    
        const mapDispatchToProps = dispatch => {
          return {
            onClick: () => {
              dispatch({type: "CLICK"});
            },
            onOtherClick: () => {
              dispatch({type: "OTHER_CLICK"});
            }
          }
        }
    
        const VisibleTitle = ReactRedux.connect( mapStateToProps, mapDispatchToProps )(Title);
    
        let store = Redux.createStore( (state = {name: '初始值', count: 0}, action) => {
          if(action.type === 'CLICK'){
            return { ...state, name: '点击之后的', count: state.count + 1};
          }
          if(action.type === 'OTHER_CLICK'){
            return { ...state, other: '什么东西'};
          }
          return { ...state };
        });
    
        const App = () => (
          <ReactRedux.Provider store={store}>
            <VisibleTitle />
          </ReactRedux.Provider>
        )
    
        ReactDOM.render(<App />, document.getElementById('app'));
      </script>
    </body>
    </html>

    上面的例子,简单来说,“纯组件”需要一个 name 来显示,而这个 name 的来自于外套组件的 store 中的 name 和 count 连在一起的。

    执行这个例子,可以看出一个重复执行的问题,当你点击第二行“另外的”那块文本时,是做的 onOtherClick 回调,这个过程并不会更改 count 值,也就是说,“纯组件”需要的 name 并不会因为这个操作而有所改变。但是, getName() 这个函数却还是一遍又一遍地执行了,这就是所谓的重复执行的问题。(如果这样的函数很多,会影响到页面性能的,特别是这些函数的逻辑有一点复杂的时候)

    其实看到这里,我个觉得 React 中的这个问题还是非常尴尬的,它自己内部的虚拟节点,靠自己的 diff 算法实现高效的更新策略,但是外面的 state 本身的维护又成了可能的问题。

    回到重复执行的问题,它并没有什么本质的解决办法,只可能进一步拆分 getName() ,分离变量的情况下,作一些执行层面的优化,简单说,先作变量是否变化的判断,然后再决定是否继续执行。这样用额外判断的成本,换取可能的省略后继执行的效益。大概是:

    const getName = ( () => {
      let lastName = null;
      let lastCount = null;
      let lastResult = null;
      return (name, count) => {
        console.log('getName');
        if(name === lastName && count === lastCount){ return lastResult }
        lastName = name;
        lastCount = count;
        lastResult = name + count;
        return lastResult;
      }
    })();

    这个样子,像是中间加了一个缓存层。

    把这个形式封装一下,就是说先把“变量”的获取提取出来,然后加一个中间缓存层。官方推荐的实现是 reselect ,做的事就是这样的: https://github.com/reactjs/reselect 。

    用 reselect 把前面的代码调整一下,就是:

    <!DOCTYPE html>
    <html lang="zh-cmn-Hans">
    <head>
    <meta charset="utf-8" />
    <title>React</title>
    <script crossorigin src="https://s.zys.me/js/jq/jquery.min.js"></script>
    <script crossorigin src="https://s.zys.me/js/react/react.min.js"></script>
    <script crossorigin src="https://s.zys.me/js/react/react-dom.min.js"></script>
    <script crossorigin src="https://s.zys.me/js/react/babel.min.js"></script>
    <script crossorigin src="https://s.zys.me/js/react/redux.min.js"></script>
    <script crossorigin src="https://s.zys.me/js/react/react-redux.min.js"></script>
    <script crossorigin src="https://s.zys.me/js/react/reselect.min.js"></script>
    </head>
    <body>
      <div id="app"></div>
      <script type="text/babel">
    
        const getName = Reselect.createSelector(
          [
            (state, props) => {console.log('reselect'); return state.name},
            (state, props) => state.count
          ],
          (name, count) => {
            console.log('getName');
            return name + count
          }
        );
    
        const Title = props => (
          <div>
            <h1 onClick={props.onClick}>{props.name}</h1>
            <h1 onClick={props.onOtherClick}>另外的 {props.name}</h1>
          </div>
        )
    
        const mapStateToProps = (state, props) => {
          console.log('mapStateToProps');
          return {
            name: getName(state, props)
          }
        }
    
        const mapDispatchToProps = dispatch => {
          return {
            onClick: () => {
              dispatch({type: "CLICK"});
            },
            onOtherClick: () => {
              dispatch({type: "OTHER_CLICK"});
            }
          }
        }
    
        const VisibleTitle = ReactRedux.connect( mapStateToProps, mapDispatchToProps )(Title);
    
        let store = Redux.createStore( (state = {name: '初始值', count: 0}, action) => {
          if(action.type === 'CLICK'){
            return { ...state, name: '点击之后的', count: state.count + 1};
          }
          if(action.type === 'OTHER_CLICK'){
            return { ...state, other: '什么东西'};
          }
          return { ...state };
        });
    
        const App = () => (
          <ReactRedux.Provider store={store}>
            <VisibleTitle />
          </ReactRedux.Provider>
        )
    
        ReactDOM.render(<App />, document.getElementById('app'));
      </script>
    </body>
    </html>

    Reselect.createSelector 是一个高阶函数,它接受的第一个参数,是一个列表,里面就是分拆出来的,获取“变量”的方法,第二个参数就是接受变量的主要逻辑。因为变量已经明确拆分出来的,所以自然地,针对变量作判断并应用缓存机制就好办了。

    这里注意一下,虽然表面上 getName() 的调用少了,但是却额外多了 reselect 的调用,本质上来说,只是改善的重复调用的损耗,并没有避免它。

    原文转载自https://www.zouyesheng.com/react.html#toc3

  • 相关阅读:
    渣渣小本求职复习之路每天一博客系列——Java基础(9)
    渣渣小本求职复习之路每天一博客系列——Java基础(8)
    渣渣小本求职复习之路每天一博客系列——Java基础(7)
    渣渣小本求职复习之路每天一博客系列——Java基础(6)
    渣渣小本求职复习之路每天一博客系列——Java基础(5)
    渣渣小本求职复习之路每天一博客系列——Java基础(4)
    渣渣小本求职复习之路每天一博客系列——Java基础(3)
    渣渣小本求职复习之路每天一博客系列——数据结构与常用算法(3)
    redis常用命令
    redis的sets类型
  • 原文地址:https://www.cnblogs.com/andyzjy/p/11040194.html
Copyright © 2011-2022 走看看