当前位置: 首页 > 科技观察

用好React必须知道的事情

时间:2023-03-20 20:26:10 科技观察

容器组件和展示组件在用React写组件的时候,我们需要有意识地将组件分为容器组件和展示组件展示组件,这有助于我们更清楚这个组件应该是什么编写组件时负责。容器组件负责业务流程逻辑的处理,比如发送网络请求,处理请求数据,并将处理后的数据传递给子组件的Props。同时,容器组件提供源数据方法,以Props的形式传递给子组件。当子组件的状态变化导致源数据发生变化时,子组件通过调用容器组件提供的方法来同步这些变化。展示组件负责组件的外观,即组件如何呈现,具有很强的内聚性。展示组件并不关心用于渲染的组件属性(Props)是如何获得的,它只需要知道在有了这些Props之后组件应该如何渲染。如何获取属性是容器组件的责任。当显示组件的状态变化需要同步到源数据时,需要调用容器组件中的方法,该方法一般通过Props传递给显示组件。例如,一个Todo项目有一个Todo组件和一个TodoList组件。Todo组件是一个容器组件,负责从服务器获取待办事项列表,获取到待办事项列表后,传递给TodoList进行展示。在TodoList中新建待办事项后,需要通过TodoList的Props调用Todo组件中保存待办事项的方法,将新的待办事项同步到服务器。容器组件和展示组件可以相互嵌套,一个容器组件可以包含多个展示组件和其他容器组件;表示组还可以包含容器组件和其他表示组件。这样的分工可以让与组件渲染不直接相关的逻辑由容器组件集中负责,而展示组件只专注于组件的渲染逻辑,这样展示组件可以更容易被复用。对于非常简单的页面,一般只需要一个容器组件就够了;但是对于负责的页面来说,需要多个容器组件,否则所有的业务逻辑都在一个容器组件中处理,会使这个组件变得非常复杂,同时这个组件获取的源数据可能需要经过很多层在到达最终显示组件之前的组件道具。Props、State的概念,以及组件Props和State的公共属性,已经很清楚了。组件的公共属性是指组件中直接挂载在this下的属性。其实Props和State也是组件的两个常用属性,因为我们可以直接通过this.props和this.state来获取。那么在什么场景下应该使用Props、State等组件的常用属性呢?Props和State都是用来进行组件渲染的,也就是说,一个组件最终长成什么样子,取决于这个组件的Props和State。Props和State的变化都会触发组件的render方法。但两者之间也有区别。Props为只读数据,由父组件传入;而State是组件自身维护的状态,是可变的。State可以根据Props的变化而变化。如果组件中还需要其他属性,而这个属性与组件的渲染无关(即不会在render方法中使用),那么这个属性可以直接挂在this下面,而不是作为一个组件的状态。比如组件中需要一个定时器,组件的状态每隔几秒就改变一次,可以定义一个this.timer属性,为componentWillUnmount时清除定时器做准备。setStateasynchronicityReact官网提到this.state和this.props的更新可能是异步的,出于性能考虑,React可能会将多个setState调用合并为一个State更新。所以,不要依赖this.props和this.state的值来计算下一个状态。引用官网的一个代码示例://Wrongthis.setState({counter:this.state.counter+this.props.increment,});如果一定要这样做,可以使用另外一个setState方法,它接受一个函数作为参数,这个函数的第一个参数是之前的State,第二个参数是当前接收到的最新的Props。如下://Correctthis.setState(function(prevState,props){return{counter:prevState.counter+props.increment};});调用setState后,不能马上用this.state获取最新状态,因为此时的状态可能还没有更新。为确保获取的state是最新状态,可以在componentDidUpdate中获取this.state。也可以使用带有回调函数参数version的setStatesetState(stateChange,[callback]),回调函数中的this.state会保证是最新状态。componentWillReceiveProps当组件的属性可能发生变化时,将调用此方法。这里是可以的,因为每次调用父组件的render方法,都会调用子组件的这个方法(子组件第一次初始化时除外),但是子组件的属性不一定每次都改变。如果组件的状态需要根据Props的变化而变化,那么这个方法就是这个逻辑最合适的地方。例如,当Props发生变化时,需要重置组件的State,可以在该方法中调用this.setState()来重置状态。需要注意的是,在该方法中调用this.setState()不会重新触发componentWillReceiveProps的调用,也不会导致render方法被触发两次。一般情况下,接收到新的Props会触发render,调用this.setState也会触发render,但是在componentWillReceiveProps中调用this.setState,React会将原本需要的两个render合并为一个。shouldComponentUpdate方法通常用于优化React性能。当shouldComponentUpdate返回false时,本次不会触发组件的render方法。可以在该方法中对比前后两个状态或props,根据实际业务场景决定是否触发render方法。React提供了一个React.PureComponent组件。该组件重写了shouldComponentUpdate,会在前后两次对state和props进行浅比较。如果不一致,则返回true并触发后续的render方法。这里的浅比较是指只比较state和props的一级属性(使用!==),满足一般的使用场景。如果你的组件继承了React.PureComponent,但是在setState的时候,传入的state是直接修改的原state对象,不会重新触发render方法,因为仍然满足浅比较的条件,导致最终的DOM和state不一致。比如state={books:['A','B']},setState时,使用this.setState({name:this.state.books.push('C')})直接修改books对象,所以虽然修改了books的内容,但是因为对象引用没有改变,所以还是满足浅比较条件,不会触发render方法。一般情况下,让shouldComponentUpdate返回默认的true问题不大。虽然这样可能会导致调用一些不必要的render方法,但是render方法是直接操作虚拟DOM的。只要虚拟DOM不发生变化,就不会引起实体DOM的修改。而JS因为修改了实体DOM,速度慢。只要你的render方法不是很复杂,多次调用render方法不会带来太大的性能开销。render组件的render方法会在每次调用父组件的render方法时触发,或者每次组件本身调用setState方法时触发(前提是shouldComponentUpdate使用默认行为并始终返回true)。那么每次渲染组件时,是否会导致重新创建实体DOM?答案是不!React之所以比直接操作DOM的JS库更快,是因为React在物理DOM之上抽象了一层虚拟DOM。执行完render方法后,就得到了虚拟DOM。React会结合当前的虚拟DOM结构和之前的虚拟DOM结构,只有在有差异的情况下,React才会将不同的内容同步到物理DOM中。如果两次渲染后的虚拟DOM结构一致,则不会触发实体DOM的修改。React速度快的另一个原因是其出色的Diff算法。用于比较两棵树的标准Diff算法的时间复杂度为O(n3)。而React基于两个非常符合实际场景的假设,将Diff算法的时间复杂度降低到接近O(n)。这两个假设是:如果两个组件或元素是不同类型的,那么它们是完全不同的树,没有必要比较它们的孩子。比如

会生成两个完整的树结构;
children

children

也是完全不同的两棵树。在这种情况下,组件被完全重建,旧的DOM节点被销毁,组件通过componentWillUnmount(),然后重新创建一棵新树,组件通过componentWillMount()和componentDidMount()。可以为一个组件或元素设置key属性,key用来标识这个组件或元素。键不需要全局唯一,只需要在兄弟组件或兄弟元素之间唯一即可。key常用于集合(List)元素中。例如:
    BookABookB
当在第一个位置插入一条记录BookC时,
    BookCBookABookB
因为有key标识,React知道新的添加一条记录会创建一个新的
  • 元素并将其插入到列表的第一个位置。如果不设置key,React不知道是增加了一条新记录,还是原来的两条记录被新的三条记录完全替换,或者其他更复杂的修改场景。React需要从上到下比较每条记录,这样每次比较的节点都不一样,所以需要修改两次节点,然后再添加一个新的节点,显然效率要低很多。这里同时暴露出另一个问题。不要使用集合中元素的索引值作为键,因为一旦集合中元素的顺序发生变化,可能会导致大量键失效,从而引起大量的修改操作。如何发送网络请求当我们需要从服务器获取数据时,应该在组件的哪个生命周期方法中发送网络请求?React官网上提到可以在componentDidMount中发送网络请求,这也是总体上最好的做法。也有人会在componentWillMount中发送网络请求,认为这个方法在componentDidMount之前调用,可以更快的获取数据。在我看来,这种使用方式一般是没有问题的,但是在某些场景下就会出现问题。例如,当需要在服务器端渲染时,componentWillMount将被调用两次,一次在服务器端,一次在客户端。你可以参考这篇文章。