React篇
React是什么
React是一个前端UI框架,通过组件化的方式解决视图层开发复用的问题,本质是一个组件化框架。
通过数据来驱动视图层进行更改,组件化的存在可以使项目尽可能的达到低耦合、高内聚
而通过学习React之后,在web和移动端都可以进行开发
怎么去设计一个组件
考虑情况——什么时候去拆分组件
分两种:
- 多个不同页面或同一页面下的相同逻辑,我们一般都会拆出来做公共组件
- 单个页面,业务逻辑复杂,拆分组件已达成单组件单业务逻辑的目的,提高开发效率以及提高代码质量
如何去做
单一职责
最基本的就是单一组件单一职责,也就是上面提到的所谓相同逻辑。
像第二种情况,一般不用考虑太多东西,只要完成A组件处理A逻辑,B组件处理B逻辑。组件接收数据,处理完成后,把数据暴露给父组件(如果需要)。
而第一种情况下,你需要考虑:既然A页面和B页面都在使用同一个逻辑,那以后在需求迭代的过程中,是否还会存在C页面D页面也需要使用它。
复用性&可维护性
这种情况下,你就需要思考:你定义的这个组件,最终结果能否让新的页面拿来即用。
不然最后在需求迭代的过程中,你编写C页面的时候,为了复用旧组件,结果导致你对旧组件的修改为了适配C页面时,还不得不跑去A页面和B页面的代码进行修改。
那岂不是更浪费时间,甚至增加了代码风险。
或者说你之前写的组件已经很不错了,不会涉及到对原有逻辑页面的修改,仅需对组件内部做一点适配。那么此时你也需要思考:这种组件内部增加逻辑分支的事情,是否达到了一个组件不该承受的重量。
举个例子吧,以前我做过一个航空公司的电商官网:
比如一张机票是否允许退票、要验证同一订单的所有乘客状态、航班状态、是否携带儿童/婴儿。退儿童/婴儿机票时,还得判断是否负责航司对于乘机人数量及类型的规定(一个大人最多带三小孩)巴拉巴拉一大堆。
当时我还是个应届生boy,我当时去维护这段代码我的头都要爆炸了。没有任何注释、判断条件复杂、分支极其之多
所以当组件内部逻辑分支过多时,就要思考是否要给组件减重了,就算真的没啥办法去减重,也务必把注释写好。
再举个细一点的例子,也是日常开发中经常遇到的场景:
A页面和B页面共用组件C。但A页面并不需要组件C的数据作为回调,但B页面需要。而这个组件是A页面编写的时候抽离出来的,当时还没有B页面(但产品已经明确告知,这个地方可能以后会有其他地方也要用)。
那么这时候对组件C的设计,就必然要考虑一个
getData
的方法,去获取内部数据。
所谓复用性大概就是这种概念。
PS:如果是TS项目的话,可以通过类型规范来保障入参的可控性
吐槽一下
其实设计组件这件事,本身是一个比较吃经验和思路的事情。这个问题简单回答的话,其实就是几个词低耦合,单⼀职责,可复⽤性,可维护性。可是落实到代码中呢,初学者根本就是一头雾水。
我其实挺讨厌这种假大空的问题,但是有时候必须要应付这些东西。
应届生在进入工作的时候,编写代码尽可能的去思考一下,如何让自己的代码能够变得更加的美观、更加的合理高效。
React的虚拟dom和diffing算法
虚拟dom
因为⾸先说说为什么要使⽤Virturl DOM,因为操作真实DOM的耗费的性能代价太⾼,所以react
内部使⽤js
实现了⼀套dom结构,在每次操作在和真实dom之前,使⽤实现好的diff算法,对虚拟dom进⾏⽐较。这样对比下来,就会知道我们需要删除/新增/修改哪些节点,最后再去更新真实的dom结构。
diff算法
首先大概描述一下react
定义的虚拟dom结构,他会有自己的dom类型、props
、子节点。
- 首先对比节点类型是否相同;如果不相同,则删除该节点再插入新节点。
- 如果相同,那么开始对比前后的
props
是否有更新,有则记录更新;再去递归子节点结构,回到第1步。 - 当dom结构完全递归结束,代表
diff
结束
细节补充版:
- 把树形结构按层级分解,只进行同层级比较
- 给列表结构的每个元素添加唯一的key属性,方便比较
- React只会进行同组件的比较,比如A组件在某次点击后被替换成了B组件,那么diff不会对组件内部在进行比较,哪怕这俩组件内部结构一致。
React Fiber
扩展提问方式
setState
调用之后发生了什么?- 触发多次
setState
,会触发几次render?
React Fiber干了什么
众所周知,JS
进程与GUI
进程是互斥的。因为diff
算法是同步的,而React
操作真实Dom渲染的appendChild
等Api
也是同步的。如果这时候存在大量节点等待渲染,就会导致JS
线程占用时间过长,导致页面卡顿。
而React Fiber就是为了解决这个问题的。它会把时间进行分割处理,当某个碎片时间到了之后,会去任务列表寻找是否有新的、优先级更高的任务需要处理。
具体过程主要在两个步骤上:
- diff阶段 其实也就是diff算法作用的过程,而这个阶段是可以被打断的,也就是上面说到的时间切片化处理优先级更高的事件。
- 渲染阶段 把之前收集到的更新渲染到真实的dom上。
而在diff阶段时,有个需要注意的点是:
有个误区是总认为一次setState
就会触发一次render
(对应React Fiber
一次渲染)。但真实情况是,React
会收集触发时间较为集中的一堆setState
,通过diff
后。只通过一次render
去渲染真实Dom。
这么干的好处是节省性能。举个例子:
五险一金计算器,你输入了工资之后,点击计算按钮,会得出若干个数字(state分开储存)。你肉眼见到的效果就是数字一个接一个的出现(这里是比较极端的),而使用了React Fiber之后,就应该是同时刷新出来。
例子不太合适,但意思应该都懂了,不懂我也当你懂了。
State和Props的区别
- State是组件内部的状态,多数时候用于对用户事件行为的存储结果。
- Props是父组件传递给子组件的配置,子组件通过对Props的获取来完成父组件交给他们的任务。
组件可以修改自身的state,但不可直接修改props。但可以通过props传递来的回调函数来修改父组件的对应数据,我不能解决问题,但我可以把提出问题的人解决掉。
函数式组件与类组件的区别
React 函数式组件和类组件的区别,不是只有state和性能! - 掘金 (juejin.cn)
个人见解:
-
在语义上,类组件的生命周期方法可读性更高;
-
在使用上来说,增加了hooks的函数式组件,更加的方便一点。
React组件的性能优化
React.memo
用法:React.memo(Component, Func)
- 第一个参数是需要性能优化的组件
- 第二个参数是判断函数是否需要更新的函数。默认是对函数的props进行浅比较。
这是组件层级上的优化
useMemo && useCallback
// 例子2
function Child({title, onChangeTitle}){
console.log('子组件渲染')
return(
<>
<div>{title.value}</div>
<button onClick={() => onChangeTitle('我是新的title')}>Button</button>
</>
)
}
const NewChild = React.memo(Child);
function Parent(){
const [count, setCount] = useState(1);
const [title, setTitle] = useState('我是子组件');
const onChangeTitle = (text) => {
dosomething()
setTitle(text)
}
const memoTitle = { value: title }
// 优化后
// const onChangeTitle = useCallback((text) => {
// dosomething()
// setTitle(text)
// },[])
// const memoTitle = useMemo(() => ({value: title}), [title])
return(
<div>
<NewChild title={memoTitle} onChangeTitle={onChangeTitle}>
<button onClick={() => setCount(count+1)}>add</button>
<div>count:{count}</div>
</div>
)
}
这两个都是组件内部对变量和函数进行性能优化的
因为组件每次render的时候,不做特殊设定的时候,都会重新定义新的变量和函数。
而这两个hook就是用来只在期望的时候重新定义。
React设计模式
「React 进阶」 学好这些 React 设计模式,能让你的 React 项目飞起来🛫️ - 掘金 (juejin.cn)