渲染原理
React其实是一个从上而下的组件树
React的jsx通过createElement会重新变成js对象,因为js的资源比DOM来的要便宜得多,当组件树都创建完成之后,会进行一个diff算法,对节点进行一个懒更新,其实他循环的时候需要的那个key属性其实就是用来更准确地找到需要更新的地方,更节省资源和效率。
当其中任何一个节点发生变化,state变化,就会对那个节点以及节点下面的子组件进行重新更新。如果想不重新更新可以用memo去包裹组件,当props没改变的情况下就可以不更新。
很多文章都有提到虚拟DOM,其实就是js的组件树对象,我最近看的一篇blog里面说React在逐渐脱离虚拟DOM的说法更倾向于称之为value ui, 意思就是把UI变成了两个可以进行比较的value。
整个过程分为两步:
1. 生成新的组件树js对象,diff比较
2. 提交改变,把对DOM进行渲染
生命周期
1. 组件初始化constructor,初始化state
2. render方法创建虚拟DOM,
3. 调用didmount钩子,这时候一般可以进行一些异步请求
4. 当state和props赋值之后会进行一个虚拟DOM更新,shouldcomponentUpdate方法判断是否需要更新
5. 然后调用didupdate钩子,这时候可以对DOM进行操做
6. component卸载会调用unmount钩子
Fiber架构粗浅理解
react 16之后引入了Fiber的重构算法,之前的是使用了递归的方式去进行diff
1. 使用了链表结构的计算方式,各个节点值之间都有联系,可以中断和回复
2. 加入了调度器,优先渲染对用户影响最大的部分
3. 时间切片可以断断续续的渲染
State理解(写给自己的)
React在渲染组件的时候更多的是考虑组件在树中的位置,如果{isShow && <MyComponent>}
这种情况下切换isShow会让组件显示和消失,他已经消失在组件树原本的位置当中了,组件中的state也会同样的销毁掉
ifA ? <MyComponent para="a" /> : <MyComponent para="b" />
这种情况下组件树中组件的位置没变,只是prop变了,react不会重置state
当state变化会触发组件的重新渲染,obj的情况需要re-assign
如果state在组件渲染期间变更则不会更新,会以最后的一次变更为准,防抖模式
State的值不会在一次渲染的内部(函数)发生变化,set之后依旧是原来的值
如果使用闭包的写法调用,则可以保存当前作用域的state数值
只读
渲染必须是纯粹的
可以使用对象展开的语法进行局部更新
需要填充新的对象进去
React在渲染组件的时候更多的是考虑组件在树中的位置,如果{isShow && <MyComponent>}
这种情况下切换isShow会让组件显示和消失,他已经消失在组件树原本的位置当中了,组件中的state也会同样的销毁掉
ifA ? <MyComponent para="a" /> : <MyComponent para="b" />
这种情况下组件树中组件的位置没变,只是prop变了,react不会重置state
需要重置的时候需要加key去让React 明白
当在相同的位置渲染相同的组件时,React 会保留状态。通过将 userId 作为 key 传递给 Profile 组件,使 React 将具有不同 userId 的两个 Profile 组件视为两个不应共享任何状态的不同组件。
React如何区别位置
{}一个大括号算一个位置
react 用key来区别不同的component
如下所示T,A,B三人分别对应一个chat聊天框,不加KEY的话聊天框的信息就共享了
Key很重要, 尤其是当需要组件一一对应的时候
Reducer
当state里面包含了过多的增改逻辑(action)的时候我们可以使用reducer封装
reducers 必须是纯粹的, 即纯函数
和用户的交互需要一一对应
Context
创建context并且传入默认值
export const LevelContext = createContext(6);
使用:const level = useContext(LevelContext);
提供:
<LevelContext.Provider value={level}>
{children}
</LevelContext.Provider>
注意
被<LevelContext.Provider value={level}>包裹的子组件
会去最近的, provider去寻找context的value
Context使用场景
主题,当前账户,状态管理
Ref
Ref是一个react框架访问不到的存储,渲染页面之后它的值也会是不变的
所以如果需要存储一些值,而这些值在显示中不会被用到,就选择Ref
ref可以接受一个函数将多个节点绑定进去
<li
key={cat.id}
ref={node => {
const map = getMap();
if (node) {
// 添加到 Map
map.set(cat.id, node);
} else {
// 从 Map 删除
map.delete(cat.id);
}
}}
>
当想要绑定Ref到自定义的组件
const MyInput = forwardRef((props, ref) => {
return <input {...props} ref={ref} />;
});
const MyInput = forwardRef(function MyInput(props, ref) {
const inputRef = useRef(null);
useImperativeHandle(ref, () => {
return {
focus() {
inputRef.current.focus();
},
scrollIntoView() {
inputRef.current.scrollIntoView();
},
};
}, []);
return <input {...props} ref={inputRef} />;
});
缓存
组件缓存
我们知道,在父组件重新渲染后,子组件也不得不渲染,就算它其实没啥变化。我们可以通过memo函数包裹子组件,达到缓存的目的,只要props没有变化就不会重新渲染
他是通过object.is比较,简单类型比较值,引用类型(数组,对象,函数)看引用是否改变,经常需要配合useMemo计算缓存和useCallback函数缓存一起用
上面的代码仍然会触发子组件渲染,因为父组件渲染后list会重新赋值
计算缓存
函数缓存
因为函数也是引用类型,当父组件把函数传递给子组件时,如果之后父组件出现了重新渲染,子组件同样得渲染。于是就需要用useCallback
父组件操纵子组件
Dom元素
如果需要父组件中获取到子组件的dom的引用,需要使用forwardRef将引用对象传递下去
调用子组件方法
如果有需求,需要父组件调用子组件的方法,可以使用useInperstiveHandler
它类似于一个把方法绑定到对象的方式,将子方法绑定到父组件的变量对象身上
Redux
slice切片
通过createSlice创建切片, name,初始化,reducer以及extraReducer用于异步请求
定义异步函数的时候需要使用AsyncThunk
Store
装载
Hooks
只在组件渲染时生效
隔离且私有
Use开头,只在组件最上面调用,或者其他hook里面调用,不能在hook的回调函数中使用
自定义 Hook 共享的只是状态逻辑而不是状态本身。对 Hook 的每个调用完全独立于对同一个 Hook 的其他调用
名字要清晰,通过名字和input就可以很好地知道这个是做什么的
路由
性能调优
1. 少用内联样式对象
2. 少用匿名函数
3. 记得用key
4. 多用懒加载和memo, useMemo
5. 隐藏显示的话最好用css实现
6. 用Fragment
Styled组件
useTransition
Zustand
SSR
服务器端渲染
首屏快,页面结构不空白有利于搜索引擎的优化
标签:调用,函数,渲染,React,state,组件 From: https://blog.csdn.net/baidu_28505395/article/details/143315286