Profiler

该组件实现了简单的测试被包裹的组件渲染的时间,主要是设置 id 属性和 onRender 函数返回相应的测试结果。其中 onRender 的返回值包括:

  • id: string - 发生提交的 Profiler 树的 id。 如果有多个 profiler,它能用来分辨树的哪一部分发生了“提交”。
  • phase: "mount" | "update" - 判断是组件树的第一次装载引起的重渲染,还是由 props、state 或是 hooks 改变引起的重渲染。
  • actualDuration: number - 本次更新在渲染 Profiler 和它的子代上花费的时间。 这个数值表明使用 memoization 之后能表现得多好。(例如 React.memouseMemoshouldComponentUpdate)。 理想情况下,由于子代只会因特定的 prop 改变而重渲染,因此这个值应该在第一次装载之后显著下降。
  • baseDuration: number - 在 Profiler 树中最近一次每一个组件 render 的持续时间。 这个值估计了最差的渲染时间。(例如当它是第一次加载或者组件树没有使用 memoization)。
  • startTime: number - 本次更新中 React 开始渲染的时间戳。
  • commitTime: number - 本次更新中 React commit 阶段结束的时间戳。 在一次 commit 中这个值在所有的 profiler 之间是共享的,可以将它们按需分组。
  • interactions: Set - 当更新被制定时,“interactions” 的集合会被追踪。(例如当 render 或者 setState 被调用时)。

以上内容引自react官网

通过一个简单的demo来展现效果:

import React, { Component, Profiler } from "react";
import styles from "./styles.module.less";

class Test extends Component {
  constructor(props) {
    super(props);
    this.state = {};
  }

  componentDidMount() {
    console.log("test");
  }
  callback = (id, ...props) => {
    let params = {
      ...props,
    };
    console.log(`查看${id}的时间`, params);
  };
  render() {
    return (
      <div className={styles.contain}>
        <Profiler id="testPro" onRender={this.callback}>
          <button>测试</button>
        </Profiler>
      </div>
    );
  }
}

export default Test;

上面的demo用于测试button组件的渲染速度,控制台打印后结果如下:image.png

不使用ES6

虽然说大概率不可能有方便的es6不用,自己去写那一长串代码,但是还是看了一下基本的。

主要是创建组件的语法差异:

类型es6不使用es6
classclass Counter extends React.Component {}var Counter = createReactClass({});
statethis.stategetInitialState: function() {return {count: this.props.initialCount};}
bindthis.handleClick = this.handleClick.bind(this) 或使用箭头函数能够自动绑定
mixin不支持支持

不使用JSX

前文已经说过了, JSX 只是 react 的语法糖,让你更加容易的进行编程,如果是自己配置的 webpack ,不想要更麻烦的配置 JSX 相关的内容,不使用 JSX 也是一样的,不过是需要将原先的 JSX 的写法改成 React.createElement() 函数创建的内容。

里面有一个较为简化的写法:

const e = React.createElement

ReactDOM.render(
  e('div', null, 'Hello World'),
  document.getElementById('root')
);

协调

这一节主要讲了 react 对于更新组件的 diffing 算法的设计决策,为了让程序更加准确的执行编写者的目的,对于一些功能的实现,部分参数的配置上,需要尽可能的协调需求满足和性能消耗

Diffing算法

  • 对比不同的根元素:如果根元素不同,直接卸载原有的根元素,重新加载新的根元素。
    生命周期如下:
    componentWillUnmount() :卸载组件,现有的 state 销毁
    componentWillMount() :新的元素将要开始加载,新的 state 初始化
    componentDidMount() :组件加载完成
  • 对比同一类型的元素:元素一致的情况下,之更改元素内属性,如果样式发生变化,之更新发生变化的那一项。
  • 对比同类型组件元素:因为类型一致,所以会保留组件实例,以保证该组件state渲染一致性,因此,内部生命周期只需调用更新的部分即可:
    componentWillReceiveProps() :当 props 发生变化时执行,内部可以使用 this.setState() 来更新 state ,此处调用更新不会触发额外的 render
    componentWillUpdate() :当 propsstate 发生变化时执行,除初始化 render 以外的 render 之后执行,内部不可以使用 this.setState() ,调用结束后会将 nextPropsnextState 更新现有 this.propsthis.state

在生命周期后,对组件内元素吊柜调用之前的 diffing ,实现整棵组件树的更新和渲染。

PS:在这个算法中,如果你在现有元素中插入一个元素,会先卸载整个根元素在重构一个新的元素,这会造成较大的性能消耗,这大概也是react不建议进行dom操作的原因之一

PS:算法在不同类型之间切换的性能消耗要远高于相同类型,因此当初输出相似的时候尽量使用同一类型的组件

Keys

当我们在使用遍历数组渲染的时候,需要设置 key 值,但是应该尽量避免使用数组的下标作为 key 值输入。因为这样做的话,可能会导致可受控组件的内容在对数组进行插入,重排时显示异常的问题。

同时,如果我们使用了诸如随机数的方式生成一个 key 值(比如使用 Math.random() )并使用它时,每一次 render 都会触发卸载元素并重构组件的操作,这会造成许多不必要的性能消耗,以及一些子组件的状态丢失问题。

Refs & DOM

前文说过了通过 Refs 转发来获取普通元素或组件元素的实例,以实现在数据流之外对 dom 进行修改(以上 refs 转发的方法仅支持 react16.3 以上版本)。

  • 适用 refs 的情况:
管理焦点,文本选择或媒体播放。
触发强制动画。
集成第三方 DOM 库。
避免使用 refs 来做任何可以通过声明式实现来完成的事情。
  • 将子组件DOM暴露给父组件
react 不建议做这样的操作,除非逼不得已
在16.3及以上版本中,可以使用 React.forwardRef 实现
在16.3以下或者想要更加灵活的使用 ref ,可以使用一些别名的 props 向下传递 ref ,比如:<Comp inputRef = { this.ref } />

回调 Refs

回调 refs 个人感觉像是在父组件中接收一个子组件的 ref 回调结果,该结果是根据子组件返回的值进行修改的,在子组件中,调用父组件的函数作为 ref ,形成一个以函数为中间件的 ref 穿透,以下代码引自https://zh-hans.reactjs.org/d...

function CustomTextInput(props) {
  return (
    <div>
 <input ref={props.inputRef} /> </div>
  );
}

class Parent extends React.Component {
  render() {
    return (
      <CustomTextInput
 inputRef={el => this.inputElement = el}      />
    );
  }
}

由上例可以看出来 CustomTextInput 组件中,调用 props.inputRef 函数作为 ref ,将该值返回追传递给了父组件 Parent 组件,最终形成了父组件使用控制 this.inputElement 获取子组件 DOM 的目的。

需要注意的是,尽量不要把回调 ref 写成内联函数的样式,因为内联函数在更新的时候会调用两次,最好就是写成类的绑定函数:

 //内联函数
<div test = {()=>{
    this.setState({ test:false })
}}>

// 类的绑定函数
constructor(props){
    this.state={ test:true }
    this.changeTest= this.changeTest.bind(this) 
}
// 或者用箭头函数
changeTest = () => {
    this.setState({ test:false })
}

Render Props

render prop 是一个用于告知组件需要渲染什么内容的函数 prop

就比如常用的,我们想要定义一个很好看的盒子,盒子里面可能装了很多不知道的东西,但是盒子的外包装是一致的,这时候我们就想要有一个组件,可以自定义接收的渲染内容,而外层不变。基于此,常用的方法是:

class Box extend Component{
    render(){
        return (
            <div style={padding:2,mrgin:4,backgroundColor:'red'}>
                {this.props.children}
            </div>
        )
    }
}

上面的例子里面,我们定义了一个红色底,内边框为2px,外边框为4px的盒子,通过 this.props.children 来接收外界传入的渲染内容,而在外界通过直接包裹的方式传入:

<Box>{'test: input a props children'}</Box>

这就是一个简单的 Render Props,在这个例子里面,我们不需要定义一个叫 childrenprop 属性,是因为 react 默认了 children 属性的含义,除非你自己手动更改了他。此外,你也可以使用自己定义的属性,如:

<Test render={(test)=>{
    return <div> {test} <div>
}}

// 定义一个和上文一样的红盒子
function Test(props){
     return (
        <div style={padding:2,mrgin:4,backgroundColor:'red'}>
            {props.render}
        </div>
    )
}

另外,使用 Render Props 可以很方便的生成一个HOC,你可以把盒子里面的渲染内容,动态的通过参数传入其中。

PS:在使用 React.PureComponent 生成组件的时候,可能会造成 Render Props 失效的问题,因为

React.PureComponent 中的 shouldComponentUpdate() 仅作对象的浅层比较。如果对象中包含复杂的数据结构,则有可能因为无法检查深层的差别,产生错误的比对结果。(引自https://zh-hans.reactjs.org/d...

这样的结果会使得 render 中每一个 props 都是不同的,这会破坏应有的渲染效果。----但是我按照官网的例子测试了好久,没有看到异常的现象:( 后续找到差异再来补充填坑


Cyearn
64 声望2 粉丝