# 基础
副作用:就是执行一系列复杂的逻辑比如ajax请求等异步工作,这类逻辑使得函数在每次执行过程中,都会产生不同的变化,这样与外界的交互统称为副作用
## jsx
jsx更像react的createElement的语法糖
return函数在babel中转换成 **react.createElement** ,而这个函数会将元素转化成react element对象,
jsx元素类型 react.createElement 转换后 type 属性
element元素类型 react element类型 标签字符串,例如 div
fragment类型 react element类型 symbol react.fragment类型
文本类型 直接字符串 无
数组类型 返回数组结构,里面元素被react.createElement转换 无
组件类型 react element类型 组件类或者组件函数本身
三元运算 / 表达式 先执行三元运算,然后按照上述规则处理 看三元运算返回结果
函数执行 先执行函数,然后按照上述规则处理 看函数执行返回结果
最终,在调和(render)阶段,这些react element对象的每一个子节点都会形成一个与之对应的fiber对象,这些fiber对象通过sibling,return,child将每一个fiber对象连接起来
//扁平化,规范children数组
const flatChildren = React.Children.toArray(reactElement.props.children)
//循环react的chilren
const newChildren = []
React.Children.forEach(flatChildren, (item)=>{
//检测是否为React element元素
if(React.isValidElement(item)) newChildren.push(item)
})
//把reactElement复制一份,再用新的children属性,从而达到改变render结果的目的
const newReactElement = React.cloneElement(reactElement,{} ,...newChildren )
## 生命周期
类组件才有生命周期函数
分为初始化,挂载阶段,更新阶段,卸载阶段
1. constructor,目前可以不用,constructor本身不属于react生命周期,这个是class初始化函数
2. getDerivedStateFromProps:作用是在props发生变化时更新state ,什么时候起效呢?当传入props时,state变化时,调用forceUpdate时
3. componentWillMount: 在引入新的fiber机制后,由于更新是异步且可以被中断的,这个生命周期可能调用多次,已被废弃
4. render: 该函数返回jsx结构,用于创建react element,该函数只是纯函数,不应在里面产生任何副作用,比如setState或者绑定事件,因为render每次渲染时都会被调用,里面的副作用会多次执行
5. componentDidMount:主要用于组件加载完成时的某些操作。在浏览器下可以认为此时页面已经加载完成,但某些场景下比如react native会由于机器的性能所限,视图可能还在绘制
6. componentWillReceiveProps: 废弃,被getDerivedStateFromProps所替代
7. getDerivedStateFromProps:和挂载阶段的一模一样
8. shouldComponnetUpdate:增加判断条件阻止不必要的渲染
pureComponent就是利用这个函数,在这个函数中对props和state进行**浅比较(仅比较值与引用,不会比较Object的每一项值)**
shouldComponnetUpdate(nextProps, nextState){
if(shadowEqual(nextProps, this.props) || shadowEqual(nextState, this.state)){
return true
}
return false
}
9. componentWillUpdate:被废弃,因为异步渲染中会有暂停更新渲染的情况
10. componentDidUpdate:getSnapshotBeforeUpdate的返回值被当作componentDidUpdate的第三个参数
11. componentWillUnMount:执行清理工作
### ErrorBoundary 错误边界
使用componentDidCatch(error, errorInfo)捕获错误
也可以使用getDerivedStateFromError捕获
#### 类组件和函数组件的区别
1. 类组件是面向对象编程,函数组件是面向函数变成
2. 类组件有生命周期,函数组件没有,但是有useEffect替代。 问题?为什么useEffect能 替代
3. 类组件有继承,函数组件没有,不过继承不推荐使用,灵活性不高。官方更推荐组合优于继承的理念
4. 类组件中更容易上手,但是因为hooks的推出,函数组件更加流行
5. 类组件使用shouldComponentupdate阻断渲染,而函数组件使用memo
## props
getDerivedStateFromProps 监听props变化
props.children可以传组件也可以传方法,所以需要判断
### 注入props
#### 显示注入props
一般直接在标签中直观看见,
#### 隐式注入props
使用reactElement.clone对props.children克隆再注入新的props
function Father(prop){
return React.cloneElement(prop.children,{ mes:'let us learn React !' })
}
## state
类组件的seState:如果是处于react生命周期以及合成事件中,setState可以算是异步,此时isBatchingupdates为true,会将多个setState放入更新队列中,然后一个一个执行。而如果不是在react控制中,比如window.addEventLisnter,setTiemout,setInterval中,此时isBatchingupdates为false,setState是同步的。这么做是为了性能,也是为了保持内部一致性,以及为了后续升级做准备。
ReactDOM.unstable_batchedUpdates装饰成isBatchingUpdates为true
## ref
类组件的ref保存位置在类的instance中维护,而函数组件由于每一次更新都是一个新的开始,所有变量重新声明,为了解决随着ref随着函数组件执行就被重置的问题,就将函数组件的ref保存在对应的fiber上,因为即使函数重新执行,只要组件不被销毁,那么对应的fiber对象就一直存在
### 高阶用法
1. forwardRef:
(1)跨层级获取,可以从祖父组件中获取到孙组件实例,就是把ref通过**props**进行**传递和转发**
(2)高阶组件转发,如果高阶组件没有处理ref,那么ref就指向了高阶组件返回的组件,而不是高阶组件返会的原始组件
2. ref 实现组件通信
(1) 就是通过ref获取实例方法再进行调用,是类组件的应用,具体参考ant 的form
(2) 函数组件 forwardRef + useImperativeHandle
3. 函数组件缓存数据
## hooks
react hooks只能在react的函数组件中使用,并且不能在循环,条件或嵌套函数中使用
之所以不能在函数组件中使用,
1是因为react hooks的设计初衷就是想改变react组件开发模式,
2是因为复杂的组件如果使用类组件来编写,会造成当前类生命周期代码越来越臃肿内聚,缺乏专注,并且本该同一体的发布订阅和取消订阅得写在不同得生命周期中,生命周期与业务逻辑耦合太深,越来越难维护。而且对于类中的this,即为何要对方法进行bind包一层既繁琐也难以理解。而且react也觉得类难以进行前端编译层优化,如常数折叠,内联展开及死码删除。
不能在循环条件或嵌套函数中使用,是因为react hooks的设计是基于数组实现的,在调用时按照顺序加入数组中,如果在循环,条件,嵌套函数中使用,很有可能会造成数组的取值错位,取出错误的hooks。当然,react源码里的这个hooks数组是一个链表。
这些限制会在新手写的时候造成一定的心智负担,新手可能会写错,为了预防这种事情,我们可以使用eslint中引入eslint-plugin-react-hooks插件来完成自动化检查进行规范,限制不在循环,条件或嵌套函数中使用
为什么顺序调用hooks很重要?
在重新渲染时,hooks会被顺序调用来保证正确识别
### useEffect 和 useLayoutEffect区别
他们的共同点都是底层调用了相同的函数签名mountEffectImpl,使用上也可以等价的替换,都是用于处理副作用。
不同点在于useEffect在react渲染过程中是被异步调用的,用于绝大多数场景。而useLayoutEffect会在所有dom变更之后同步调用,主要用于dom操作,调整样式,避免页面闪烁。也正是因为是同步调用,所以要避免在useLayoutEffect中调用计算量很大的任务,造成页面阻塞
## context
创建 const Context = React.createContext({})
提供者 <Context.Provider value={contextValue}> </Context.Provider>
消费者
① 类组件之contextType 方式
② 函数组件之 useContext 方式
③ 订阅者之 Consumer 方式
const ThemeConsumer = ThemeContext.Consumer // 订阅消费者
{ /* 将 context 内容转化成 props */ }
{ (contextValue)=> <ConsumerDemo {...contextValue} /> }
### 高阶用法
1.逐层传递Provider
// 逐层传递Provder
const ThemeContext = React.createContext(null)
function Son2(){
return <ThemeContext.Consumer>
{ (themeContextValue2)=>{
const { color , background } = themeContextValue2
return <div className="sonbox" style={{ color,background } } > 第二层Provder
} }
</ThemeContext.Consumer>
}
function Son(){
const { color, background } = React.useContext(ThemeContext)
const [ themeContextValue2 ] = React.useState({ color:'#fff', background:'blue' })
/* 第二层 Provder 传递内容 */
return <div className='box' style={{ color,background } } >
第一层Provder
<ThemeContext.Provider value={ themeContextValue2 } >
</ThemeContext.Provider>
}
export default function Provider1Demo(){
const [ themeContextValue ] = React.useState({ color:'orange', background:'pink' })
/* 第一层 Provider 传递内容 */
return <ThemeContext.Provider value={ themeContextValue } >
</ThemeContext.Provider>
}
Provider 特性总结:
1 Provider 作为提供者传递 context ,provider中value属性改变会使所有消费context的组件重新更新。
2 Provider可以逐层传递context,下一层Provider会覆盖上一层Provider。
现在使用**useContext**
### 其他api
1. displayName
context 对象接受一个名为 displayName 的 property,类型为字符串。React DevTools 使用该字符串来确定 context 要显示的内容。
const MyContext = React.createContext(/* 初始化内容 */);
MyContext.displayName = 'MyDisplayName';
<MyContext.Provider> // "MyDisplayName.Provider" 在 DevTools 中
<MyContext.Consumer> // "MyDisplayName.Consumer" 在 DevTools 中
react-redux 就是通过 Provider 模式把 redux 中的 store 注入到组件中的。
## HOC
两种方式实现
1. 属性代理 组件包裹一层代理组件,代理组件可以强化源组件并直接return 源组件
2. 反向继承 包装后的组件继承了原始组件本身,无须再去挂载业务组件。
### 高阶组件功能说明
1. 强化props
2. 控制渲染
(1)、渲染劫持:HOC 反向继承模式,可以通过 super.nt 对象。 修改渲染树,也是根据判断render() 得到 render 之后的内容,利用这一点,可以做渲染劫持 ,更有甚者可以修改 render 之后的 React eleme需要渲染什么组件,最后使用React.cloneELement(props.render(),element.props, newchild)
(2)、动态加载:
3. 注意事项
(1)、 谨慎修改原型链
(2)、不要在类组件的render函数或者函数组件里使用HOC,每一次触发类组件的render函数或者函数组件执行,都会生成新的HOC返回新组件,react diff会判定两次不是同一个组件,会卸载掉老的组件挂载新的组件。造成性能浪费
例如:
`function Index(){
const WrapHome = HOC(Home)
return
}`
(3)、ref处理
(4)、HOC嵌套顺序:要注意一下包装顺序,越靠近 **Index** 组件的,就是**越内层**的 HOC ,离组件 Index 也就越近。
(5)、继承静态属性,属性代理的方法就是返回了一个新的component,如果给原来的component绑定一些静态属性方法,如果不处理,那么新的component就会丢失,我们可以手动绑定,每个静态属性方法都手动绑定肯定会很累,尤其对于开源的 HOC ,对原生组件的静态方法是未知 ,为了解决这个问题可以使用 hoist-non-react-statics
4. 应用 **权限管理**
# 优化
## 渲染控制
react本身已有很多优化,diff,fiber,时间分片,设立任务优先级,异步调度,我们只需要告诉react哪些需要更新,哪些不需要更新。
所以优化只是告诉react哪些需要更新,哪些不需要更新,所以也就是控制render的方式
1. 从父组件直接隔离子组件的渲染,经典的memo,缓存element对象
(1)、{ useMemo(()=> ,[ numberA ]) }
**useMemo原理**:会记录上一次执行create的返回值,并把他绑定在函数组件对应的fiber上,只要组件不销毁,缓存值就会一直存在,但是依赖项中有一项被改变,就会重新执行create,返回值作为新的值记录到fiber对象上
**react更新原理**:createElement会产生一个新的props作为fiber的penddingProps,在此fiber更新调和阶段,会将上一个老的props与这个pendingProps比较,如果不相同则重新渲染,相同则不重新渲染,因为函数式组件是每一次更新都是执行一次函数,里面的变量其实都是另一个新的变量。useMemo就是利用这个原理,将element缓存起来,此时props也就和fiber上的oldProps指向相同的内存空间,也就跳过本次更新
(2)、useCallback
export default function (){
const callback = React.useCallback(function handerCallback(){},[])
return
}
**useMemo和useCallback区别**
useMemo需要执行第一个函数,返回值为缓存的内容,更像是缓存了一段执行逻辑后返回的结果
useCallback第一个参数就是缓存的内容
2. 组件从自身判断控制是否render,比如PureComponent,shouComponentUpdate
(1)、PureComponent:浅比较state和props是否相等。浅比较只会比较基本的数据类型,对于修改了对象的属性是不会促使组件更新的。可以使用浅拷贝解决这个问题。
changeObjNumber=()=>{
const { obj } = this.state
obj.number++
this.setState({ obj:{...obj} })
}
**PureCOmponent原理**
(2)、shouldComponentUpdate** 已废弃
(3)、React.memo:React.memo(COmponent, compare),第二个参数是一个函数,可以根据一次更新中的props是否相同决定原始组件是否重新渲染, 返回 true 组件不渲染 , 返回 false 组件重新渲染,memo 当二个参数 compare 不存在时,会用浅比较原则处理 props ,相当于仅比较 props 版本的 pureComponent
## 渲染调优
1. 懒加载和异步渲染,两者一起使用效果更加
(1)、 异步渲染
Suspense
fallback属性就是在loading的时候展示的内容,suspense的子组件就是异步组件
(2)、 懒加载
Lazy
2. 渲染错误边界
(1)、componentDidCatch修改state
(2)、getDerivedStateFromError替代componentDidCatch,由于是静态方法,不能直接修改state,所以getDerivedStateFromError返回的值可以合并到state
3. diff中key的使用
合理的使用key可以精准的找到用于新节点复用的老节点
## 细节优化
1. 防抖节流,可以配合使用useCallback或者useMemo缓存来提高性能
2. 按需引入,
# 原理篇
## 调度(schduler)和时间分片
时间片:浏览器在一帧中做的事情,处理事件,执行js,执行requestAnimation,布局,绘制,如果没有其他事,浏览器就会进入空闲时间。
javascript线程和gui线程是互斥的,如果执行js线程太久,就会导致浏览器没有时间进行样式绘制导致卡死。所以react使用messageChannel模拟一个requestIdelCallback,将同步任务变为可中断的异步任务,一帧一帧的向浏览器请求,等到浏览器有空闲时间后可以执行。
因爲requestIdelCallback目前只有chrome支持,所以react需要寫一個模拟requestIdelCallback。模拟一个requestIdelCallback需要实现两个功能。需要能让出主线程执行,让浏览器去渲染线程,需要保证每一个事件循环只执行一次,执行完事件循环后,需要请求下一个时间片。能这么完成的只有宏任务。排除setTiomeout是因为递归执行时会有4ms的误差,所以选择MessageChannel。
**调度**:通过时间分片获取到空闲时间片,接收到更新,并执行高优先级任务更新。**最后将其转交给reconciler**
### MessageChannel
### 异步调度原理
## 调和器(reconciler)
负责找出变化的组件,会为发生变化的虚拟dom打上增删改查的标记,当所有的组件都完成了reconciler的工作,就会统一交给rederer。
新的reconciler从递归变为可中断的循环过程, 每次循环都会调用shouldYield判断当前是否有剩余时间
fiber reconciler的特点是:协作多任务模式,基于循环遍历计算diff
### render阶段
构建workInProgress树来计算diff
#### beginWork
#### completeWork
## 渲染器(renderer)
接收到Reconciler的通知,对被添加增删改查标记的虚拟dom执行对应的操作, 负责将变化的组件渲染到页面上
### commit阶段
处理effect列表,这里的effect列表包含了diff更新dom树,回调生命周期,响应ref等
#### before mutation
整个过程就是遍历effectList并调用commitBeforeMutationEffects函数处理。
commitBeforeMutationEffects:
1. 处理DOM节点渲染/删除后的 autoFocus、blur 逻辑。
2. 调用getSnapshotBeforeUpdate生命周期钩子。
3. 调度useEffect。
#### mutation
#### layout
react渲染过程大致一致,但协调并不相同,以react16为分界线,分为stack reconciler和fiber reconciler,通常包含了diff算法和公共逻辑,stack reconciler的核心调度方式是递归,基本调度单位是事务,他的事务基类是transaction,这里的事务是从后端开发中加入的概念。在react16以前,挂载主要由reactMount(reactMount.render()完成实际初始化React组件的整个过程),更新由reactUpdate完成,模块之间相互分离。在react16之后,协调改为了fiber reconciler,它有两个特点,第一个是多任务协作模式,在这个模式下,线程会定时放弃自己的运行权力,交还给主线程,通过requestidelCallback实现。第二个特点是策略优先级,调度任务通过标记tag的方式分优先级执行,如动画。fiber reconciler的基本单位是fiber,fiber基于react element提供二次封装,提供了指向父、子、兄弟节点的引用,为diff工作的双向链表实现了基础。在新的架构下,整个生命周期划分成了render和commit两个阶段。render阶段的特点是可中断停止无副作用,主要是通过构造workInProgress树计算diff,以current树为基础,将每个fiber作为一个基本单位,自下而上的逐个节点检查并构造workInProgress树,这个过程不再是递归,而是基于循环来完成。在执行上,requestidelCallback来调度执行每组任务,每组的每个计算任务被称为work。每个work完成后确认是否有优先级更高的work需要插入,如果有,就让位,如果没有,就继续。优先级通常是标记为动画会先做处理,每完成一组后,将调度全交还给主线程,等下一次requestIdelCallback调用再继续调用workInProgress树。在commit阶段,主要处理effect列表,effect列表包含了根据diff更新dom树,回调生命周期。这个阶段是的同步执行的,不可中断的,所以不要在componentDidMount,componentDidUpdate,componentWillUnmount执行重度消耗算力的任务。
在管理后台,h5展示页等,两者性能差距并不大,但是在动画画布及手势等场景下,stack reconciler的设计会占用主线程,造成卡顿,而fiber reconciler会带来高性能的表现。
## 事件系统
为什么要有一套自己的事件系统?因为为了抹平浏览器之间的差异,其次,react事件是绑定在document或者容器上,这个减少了不可控的情况,但同时也需要配react模拟出一套捕获,事件源,冒泡的阶段,也包括重写事件源对象event
阻止默认行为: e.preventDefault()
阻止冒泡:e.stopPropagation(),17之前因为将事件绑定在了document上,造成了**重合**,导致无法使用stopPropagation进行阻止冒泡
1. 如何阻止冒泡
当使用stopPagation()来阻止冒泡时,那么事件源里将会有一个状态证明此次事件已经停止冒泡,那么下次遍历的时候,react内部通过event.isPropagationStopped()函数来判断是否需要阻止,如果是,就会break跳出循环,其他的事件函数将不再执行
```function runEventsInBatch(){
const dispatchListeners = event._dispatchListeners;
if (Array.isArray(dispatchListeners)) {
for (let i = 0; i < dispatchListeners.length; i++) {
if (event.isPropagationStopped()) { /* 判断是否已经阻止事件冒泡 */
break;
}
dispatchListeners[i](event) /* 执行真正的处理函数 及handleClick1... */
}
}```
### 合成事件
react中的事件比如onChange是blur,change,focus事件合成的,每个事件都对应着一个原生事件数组
#### 事件插件机制
不同的事件都有其对应的事件插件进行处理,不同的事件都有不同的逻辑,也因为事件和事件源都是react自己合成的,所以需要事件插件机制
### 事件绑定
react会将事件保存在对应dom节点的fiber的memoizedProps属性上memoizedProps = {onClick: handleClick},而挂在在document的事件处理函数并不是写的事件方法名如handleClick,而是dispatchEvent,由dispatchEvent进行统一处理
### 源码流程
1. react的初始化真实dom的时候,会用一个指针指向fiber,fiber有一个statenode指向真实dom,
## hooks原理
hooks 可以作为函数组件本身和函数组件对应的 fiber 之间的沟通桥梁。因为函数式组件
## virtual dom
React.createElement() 根据真实dom模拟返回的一个对象就是虚拟dom。react会持有一颗虚拟dom树,当状态发生改变时,会触发虚拟dom树的修改,再以此为基础修改真实dom
跨平台成本低
有效避免xss攻击,因为保留了danderouslySetInnerHTML 的api
内存占用高
无法进行极致优化
### dom diff
大量的操作真实dom容易引起网页性能下降,这是React基于虚拟dom的diff处理与批处理操作可降低dom的操作范围和频次,提升页面性能
## react渲染流程
# 生态篇
## redux React-Redux
为什么需要状态管理库?
**因为context很难追踪数据源以及确认变动点**
redux是状态管理js库,而React-Redux是沟通react和redux的桥梁,react-redux负责将redux的store注入react应用中,并且派发store的更新消息到每一个需要状态的组件(对于完整的状态管理生态,大家可以尝试一下 dvajs)
react应用dispatch到redux, react-redux订阅redux的消息并将state注入到react应用
1. redux(action:触发事件,state:管理UI状态,reducer:处理业务逻辑)
(1)、是单向数据流,并且state**只读**,只能通过action进行更改,action会执行每个reducer,reducer是一个纯函数,里面不要执行任何副作用,返回值作为新的state,state改变会触发store中的subscribe(订阅)
(2)、采用发布订阅思想,redux有一个store,里面存放了状态信息,以及改变store的方法dispath,和订阅sotre改变的subscribe
(3)、运用了中间件思想,可以用来**强化distpatch**。因为传统的dispath不支持异步,所以一些redux-thunk,redux-actions中间件,本质上应用了函数柯里化
缺点:1.书写不方便,需要一直写Store.subScribe以及unsubScribe
2. 更改了某个组件a需要的状态a,可能会导致需要b状态的b组件也进行更新
2. react-redux
为了改变以上缺点react-redux应运而生
原理:
(1)、 Provider注入Store
(2)、
(3)、
### 中间件
1. redux-thunk
//如果是数组或者是promise就不执行,直接执行下一个
function createthunkMiddleware(extraArgument){
return ({dispatch, getState}) => (next) => (Action) => {
if(typeof Action === 'function'){
return Action(dispatch, getState, extraArgument);
}
return next(Action)
}
}
const thunk = createthunkMiddleware()
thunk.withExtraArgument = createthunkMiddleware
export default thunk
2. redux-saga
## mobx
## react-router
# react渲染流程