• 前言

    从入职起开始学习React框架,到今天快一个月了。这段时间里,除了日常维护公司的代码,同时跟进了公司内部的一个项目,也开始尝试构建自己的小东西了。在这之前,我的技术栈还是Vue和Svelte这样的模板语法的前端框架,上手React之后感觉区别还是比较大的。

1. 组件和数据流

  • react使用组件的传值方式其实和Vue大同小异,父组件 -> 子组件依然是props;子组件 -> 父组件则是通过在子组件中调用一个父组件传来的回调函数来实现;跨组件的传值则是通过useContext这个钩子来实现的。
  • 数据流方面,组件之间是单项数据流。父组件 --props--> 子组件 --state--> DOM。和Vue不同,组件到DOM依然是单项数据流,没有所谓的双向绑定操作。因此,在React中,onChangesetState是经常使用的监听器和钩子。

2. 渲染过程

  • React的特点就是当state改变的时候(注意不是props),子组件就会rerender;而我们在开发过程中,几乎所有的数据都会放进state里,如果没有相应的性能优化,必然导致很多无意义的rerender操作,增大应用的性能开销。
  • 在不使用函数组件的时候,React生命周期中的shouldComponentUpdate是可以直接被控制的,而在函数式组件写法中,我们需要通过别的方法来优化性能:
    1. 使用memo,当我们更新state的时候,如果子组件中props并没有改变,那么子组件就不需要被rerender。我们只需要在子组件外面套一层memo就可以了。如果我们需要自定义两个props的比较器,只需要多传入一个返回bool值得areEqual函数即可。
    	React.memo((prpos) => {},areEqual)
    	const areEqual = (prevProps, nextProps){}
    
    1. 使用useCallback,当我们的props是Object时,第一种方法就失效了,因为memo是shallowly compare,熟悉js引用类型和深浅拷贝就知道,这样比较无论如何都会使得子组件被rerender。所以这时候我们就需要用useCallback来让React记住props的地址,如果dependency array(如下代码中的[a,b])里面的值没有被修改,那么props的地址就不会改变,从而做到减少子组件的rerender。
    	const useCallbackFunc = useCallback(() => {},[a,b])
    
    1. 使用useMemo,有的时候,我们重新渲染一个组件时,这个组件中的某些高开销函数的返回值并没有变化(比如canvas绘制地图)。那么我们可以使用useMemo将这个函数包裹起来,React就会在重新渲染组件的时候判断useMemo的dependency array中的值是否变化,来决定重新调用这个函数or继续使用上一次的返回值,从而达到性能优化的目的。
    	const memoizedValue = useMemo(
    	() => computeExpensiveValue(a, b), [a, b])
    

3. 状态管理

  • Vue的状态管理基本基于Vuex,并且,在很多非大型应用中完全可以不使用状态管理。但是React本身是基于state的,无论如何都需要进行状态管理,因此,设计一套合理的状态是相当重要的一步。
  • 目前我个人而言,使用最多的还是官方介绍的“状态提升”和使用全局状态,即useContext,但是在编写复杂逻辑时,还是经常出现状态混乱的情况,需要多次重构,反复思考才能设计出更优秀的状态方案。

4. JSX语法

  • JSX语法不同于Vue这类模板语法,React会把jsx转换为原生js来执行,而不是一种HTML扩展。因此,在jsx中,我们返回的那些看起来像HTML标签的东西实际上完全不同于Vue的模板,他们应该被看作一个js的回调函数,这样一层层的嵌套下去。最终React通过这些函数的返回值来构建V-DOM,通过比较V-DOM的diff来判断是否更新DOM。这也正是React没有v-model v-if v-for这样直接写在那些看起来像HTML标签里面的语法糖的原因,取而代之的,是在return的代码中,直接使用原生js来控制要返回的东西(如if-else array.map()),从而实现更高的性能。

  • 最后

    确实比较惊讶,自己能在不到一个月的时间里上手一个从来没接触过的框架,并且从中学到很多东西,对于前端的应用构建方式也有了新的思考。总体看来React应该还是比较容易上手,但是想写好React,尤其是写出高性能,可维护的代码还需要很长时间的经验积累。


What is broken can be reforged.