-
前言
从入职起开始学习React框架,到今天快一个月了。这段时间里,除了日常维护公司的代码,同时跟进了公司内部的一个项目,也开始尝试构建自己的小东西了。在这之前,我的技术栈还是Vue和Svelte这样的模板语法的前端框架,上手React之后感觉区别还是比较大的。
1. 组件和数据流
- react使用组件的传值方式其实和Vue大同小异,父组件 -> 子组件依然是props;子组件 -> 父组件则是通过在子组件中调用一个父组件传来的回调函数来实现;跨组件的传值则是通过
useContext
这个钩子来实现的。 - 数据流方面,组件之间是单项数据流。父组件 --props--> 子组件 --state--> DOM。和Vue不同,组件到DOM依然是单项数据流,没有所谓的双向绑定操作。因此,在React中,
onChange
和setState
是经常使用的监听器和钩子。
2. 渲染过程
- React的特点就是当state改变的时候(注意不是props),子组件就会rerender;而我们在开发过程中,几乎所有的数据都会放进state里,如果没有相应的性能优化,必然导致很多无意义的rerender操作,增大应用的性能开销。
- 在不使用函数组件的时候,React生命周期中的
shouldComponentUpdate
是可以直接被控制的,而在函数式组件写法中,我们需要通过别的方法来优化性能:- 使用
memo
,当我们更新state的时候,如果子组件中props并没有改变,那么子组件就不需要被rerender。我们只需要在子组件外面套一层memo
就可以了。如果我们需要自定义两个props的比较器,只需要多传入一个返回bool值得areEqual
函数即可。
React.memo((prpos) => {},areEqual) const areEqual = (prevProps, nextProps){}
- 使用
useCallback
,当我们的props是Object时,第一种方法就失效了,因为memo是shallowly compare,熟悉js引用类型和深浅拷贝就知道,这样比较无论如何都会使得子组件被rerender。所以这时候我们就需要用useCallback
来让React记住props的地址,如果dependency array(如下代码中的[a,b])里面的值没有被修改,那么props的地址就不会改变,从而做到减少子组件的rerender。
const useCallbackFunc = useCallback(() => {},[a,b])
- 使用
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,尤其是写出高性能,可维护的代码还需要很长时间的经验积累。
Comments | 0 条评论