fiber
为了使react渲染的过程可以中断。可以将控制权交给浏览器,可以让位给高优先级的任务,浏览器空闲后再恢复渲染
对于计算量比较大的js计算或者dom计算,就不会显得特别卡顿,而是一帧一帧有规律的执行任务
generator(es6中可以灵活用来中断和继续的方法)
const tasks = []
function * run(){
let task;
while(task = task.shift()){
// 有高优先级任务时中断
if(hasHeighPriorityTask()){
yeild
}
execute(task)
}
}
const iterator = run()
iterator.next()
- generator有类似的功能,为什么不直接使用
- 要是用generator,需要将涉及到的所有代码都包装成genetator * 的形式,非常麻烦,工作量很大
- generator内部是有状态的,具体例子如下
function * doWork(a,b,c){
const x = doExpendsiveWorkA(a)
const y = doExpendsiveWorkB(b)
const z = doExpendsiveWorkC(x,y,c)
return z
}
我们已经执行完了doExpendsiveWorkA和doExpendsiveWorkB,还未执行doExpendsiveWorkC。如果此时 b被更新了,那么在新的时间分片里,我们只能沿用之前获取到的x,y结果==>不符合预期 2. 如何判断当前是否有高优先任务 当前js的环境其实并没有办法来判断是否有高优任务
只能约定一个合理的时间,当超过了这个执行时间,如果任务仍然没有执行完成,中断当前任务,把控制权交给浏览器
16ms是一个相对合理的时间,而requestIdleCallback恰好就可以做这个合理的时间, 时浏览器在有空的时候执行我们的回调,这个回调会传入一个参数,表示,浏览器有多少时间执行我们的任务
- 浏览器在一帧内要做什么事情
- 处理用户的输入事件
- js的执行
- requestAnimation 调用
- 布局 layout
- 绘制 paint 如果浏览器在16ms以内执行完上述事件,则剩下的事件就是空闲事件,
- 浏览器很忙怎么办 requestIdleCallback timeout参数,100ms,如果超过这个timeout后,回调还没有被执行,那么会在下一帧强制执行回调
- 兼容性 requestIdleCallback兼容性非常差,通过messageChannel来模拟实现了requestIdleCallback的功能
- timeout超时后一定要被执行吗
不是的,react里预定了五个优先级的等级
- Immediate 最高优先级,这个优先级的任务应该马上执行不能中断
- userBlocking,比如点击按钮这些用户交互,需要及时得到反馈
- Normal 不需要用户立即感受到的变化,比如网络请求
- low,这些任务可以延后,但是最终也需要执行
- idle,可以被无限期延后
平时用过高阶组件吗,什么是高阶组件,高阶组件能用来做什么
简称HOC,High Order Components
- 是一个函数
- 入参:原来的react组件
- 返回值,新一个新的react组件
- 是一个纯函数,不应该有任何的副作用
function helloWorld(myName){
console.log(`hello beautiful world, my name is ${myName}`)
}
function byeWorld(myName){
console.log(`bye ugly world, my name is ${myName}`)
}
function wrapWithUsername(wrappedFunc){
const tempFunction = ()=>{
const myName = sessionStorage.getItem('lily')
wrappedFunc(myName)
}
return wrappedFunc
}
const wrappedHello = wrapWithUsername(helloWold)
wrappedHello()
怎么写一个高阶组件
- 普通方式
- 装饰器
export const decoratorWithNameHeight = (height?:number)=>{
return (WrappedComponent: any) =>{
return class extends Component<any, State>{
public state: State = {
name: ''
}
componentWillMount(){
let username = localStorage.getItem('myName')
this.setStata({
name: username || ''
})
}
render(){
return(
<div>
<WrappedComponent name={this.state.name} {...this.props}>
<p>身高为{height||0}</p>
</div>
)
}
}
}
}
import {decoratorWithNameHeight} from '../hoc/index'
@decoratorWithNameHeight(180)
class UglyWorld extends Component<Props,any>{
render(){
return <div>bye,ugly world</div>
}
}
- 多个高阶组件的组合
// 如上,多个的时候应是
@decoratorWithNameWidth(100)
@decoratorWithNameHeight(180)
class UglyWorld extends Component<Props,any>{
render(){
return <div>bye,ugly world</div>
}
}
高阶组件能用来做什么,技术层面上
- 属性代理
- 操作props(上边的例子就是操作props)
- 操作组件实例
export const refHooc = ()=>{ return (WrappedComponent)=>{ return class extends Component{ componentDidMount(){ // 这里就可以劫持到实例的属性,state,methods console.log(this.ref.state) } render(){ return ( <WrappedComponent {...this.props} ref={(instance)=>{ this.ref = instance }} /> ) } } } } @refHoc class RefDemoComponent extends Component{ state = { weight : 60 height: 170 } render(){ return <div>bye ugly world my name is {this.props.name}</div> } }- 继承/劫持
扩展: connect原理// render劫持 export function hijackHoc<T extends {new(...args: any[]):any}>(component){ return class extends component{ handleClick(){ super.handleClick() } render(){ const parent = super.render() return React.cloneElement(parent,{ onClick:()=>this.handleClick() }) } } }- Provider: Provider的作用是从最外部封装了整个应用,并向connect模块传递store
- connect: 负责连接React和Redux
// 1.store.substrubite(() => {}) 和 unsubscribe(); // 2.{ ...mapStateToProps( store.getState() ) }; // 3. {...mapDispatchToProps(store.dispatch)}; import React from "react"; import store from "./store"; const connect = (mapStateToProps, mapDispatchToProps) => { return (Wrapper) => { // Wrapper 传递进来的组件 class WrapperFragment extends React.Component { constructor(props) { super(props); this.state = { // 连接store并初始化state storeState: { ...mapStateToProps(store.getState()) }, }; } componentDidMount() { this.unsubscribe = store.subscribe(() => { // 组件订阅 store的更新 this.setState({ storeState: { ...mapStateToProps(store.getState()) }, }); }); } componentWillUnmount() { // 组件卸载时取消对store的更新 this.unsubscribe(); } render() { return ( <Wrapper // 传递参数 {...this.props} // 将store.getState() 获取到的参数作为porps传递进 组件 {...mapStateToProps(store.getState())} // 将store.dispatch() 获取到的更新store的方法作为porps传递进 组件 {...mapDispatchToProps(store.dispatch)} /> ); } } return WrapperFragment; }; }; export default connect;什么是React hooks? React hooks有什么优势
hooks: 可以在不写class组件的情况下,使用state和其它react特性 eg: useState,useEffect,useMemo 为什么不写class而是转向了hooks的写法react hooks的优势
class的缺点-
组件间的状态逻辑很难复用 组件间如果有state的逻辑是相似的,class模式下基本上可以用高阶组件来解决 虽然能够解决问题,但是需要在元素,组件外部再包一层会致层级非常冗余
-
复杂业务的有状态组件会越来越复杂
-
监听和定时器的操作,被分散在多个区域
-
this的指向问题
render中绑定函数,会使每次都返回一个新的函数,造成组件每次都渲染,资源浪费
hooks的优点
- 利于业务逻辑的封装和拆分,可以非常自由的组合各种自定义hooks,
- 可以在无需修改组件结构的情况下,复用状态逻辑
- 定时器,监听等都被聚合到同一块代码下
hooks的使用注意事项
- 只能在函数内部的最外层调用hook,不要在循环,条件判断或者子函数中调用
- 只能在react的函数组件中调用hook,不要再其它的js函数里调用
问题
- 为什么,hooks不能在循环和条件判断中调用
- 为什么useEffect的第二个参数是空数组,就相当于componentDIdmout只执行一次
- 自定义的hook怎么操作组件的
手写代码实现useState
function Counter(){ const [count,setCount] = useState(0) const onClick = ()=>{ setCount(count+1) } return ( <div> <div>{count}</div> <button onCLick={onClick}>点击</button> </div> ) } let stateArray: any[] = [] let cursor: number = 0 function useState<T>(initialState:T):[T,(newState:T)=>void]{ const currentCursor = cursor stateArray[currentCursor] = stateArray[currentCursor] || initialState setState = (newState: T)=>{ stateArray[currentCursor] = newState } ++cursor return [stateArray[currentCursor],setState] } export function render(){ ReactDom.render( <ReactDOM.StrictMode> <Counter /> </ReactDOM.StrictMode>, document.getElementNyId('root') ) cursor = 0 // 每次渲染把游标值为0 }手写代码实现useEffect
// 同上边的例子,增加useEffect调用 useEffect(()=>{ console.log('count发生了改变'+count,) },[count]) const allDeps: Array<any[] | undefined> = [] let effectCursor: number = 0 function useEffect(callback: ()=>void,depArr?: any[]){ if(!depArr){ callback() allDeps[effectCursor] = depArr effectCursor++ return } const deps = allDeps[effectCursor] const hasChanged = deps ? depArr.some((el,i)=>el !== deps[i]): true if(hasChange()){ callback() allDeps[effectCursor] = depArr } effectCursor++ }