React hook 以及 React Fiber 原理

hook使用规则
选择使用Hook原因
React Hooks的几个常用钩子

hook使用规则:只能在最顶层使用hook,不要在循环,条件或者嵌套函数中调用Hook,确保总是在你的react函数的最顶层调用他们,只在react函数中调用Hook不要在普通的Javascript函数中调用Hook,在React的函数组件中调用Hook在自定义Hook中调用其他的Hook。npm install eslint-plugin-react-hooks –save-dev ESLint 插件来强制执行这两条规则
2021071506545066
 
 
选择使用Hook原因:在组件之间复用状态逻辑很难,复杂组件变得难以理解,用更少的代码,实现同样的效果
React Hooks的几个常用钩子:useCallback的功能完全可以由useMemo所取代,useCallback(fn,inputs) === useMemo(()=>fn,inputs)
1.useState() //状态钩子
2.useContext() //共享状态钩子
3.useReducer() //action钩子
4.useEffrct() //副作用钩子useEffect在浏览器渲染完成后执行
5.useCallback() //记忆函数
6.useMemo() //记忆组件
7.useRef() //保持引用值
8.useImperativeHandle() //穿透ref
9.useLayoutEffect() //同步执行副作用,不常用,useLayoutEffect在浏览器渲染前执行
10.自定义hook //以useXX开头的,封装重复使用的代码提高复用性
 

import React, { useState, useEffect, useRef, useReducer, useCallback, useMemo, forwardRef, useImperativeHandle, Component } from 'react';
const HookComp = () => {
const size = useChangeSize();
const [count, setCount] = useState(0);
const [title, setTitle] = useState('标题')
let timer = useRef();
const ref = useRef();
// useEffect 就是一个 Effect Hook,给函数组件增加了操作副作用的能力。
// 它跟 class 组件中的 componentDidMount、componentDidUpdate 和 componentWillUnmount 具有相同的用途,只不过被合并成了一个 API
// 相当于 componentDidMount 和 componentDidUpdate:
useEffect(() => {
timer.current = setInterval(() => {
console.log(1)
},1000)
document.title = `You clicked ${count} times`;
return () => {
clearInterval(timer.current) //相当于 componentWillUnmount
} },[count]);
useEffect(() => { ref.current.open() },[])
return (
<div>
     <p>You clicked {count} times</p>
     <button onClick={() => setCount(count + 1)}> Click me </button>
     <input type="text" value={title} onChange={e => setTitle(e.target.value)} />
     <p>{title}</p>
     <div>页面大小{size.width}*****{size.height}</div>
     <TimeTest/>
     <TestRef ref={ref}></TestRef>
     <TestRef1 ref={ref}></TestRef1>
     <BookkList/>
</div>
)
}
// useReducer 用法
function BookkList(){
   const inputRef = useRef();
   const [items, dispatch] = useReducer((state,action)=> {
     console.log(state,action)
     switch(action.type){
        case 'add':
        return [ ...state, { id: state.length, name: action.name } ]
        case 'del':
           return state.filter((_,index) => index != action.index) } },[])
           function handleAdd(event){
             event.preventDefault();
             dispatch({ type:'add', name:inputRef.current.value });
             inputRef.current.value = '';
            }
             return (
             <>
             <form> <input ref={inputRef}/>
             <button onClick={ handleAdd }>点击添加</button>
             </form>
             <ul>
               { items.map((item, index) => (
               <li key={item.id}> {item.name}
                <button onClick={ () => dispatch({type:'del',index})}>点击删除</button>
               </li> ))
               }
              </ul>
              </>
              )
            }
            /* useRef是一个方法,且useRef返回一个可变的ref对象 修改 ref 的值是不会引发组件的重新 render useRef非常常用的一个操作,访问DOM节点,对DOM进行操作,监听事件等等 ref 在所有 render 都保持着唯一的引用,因此所有的对 ref 的赋值或者取值拿到的都是一个 最终 的状态,而不会存在隔离 使用 useRef 来跨越渲染周期存储数据,而且对它修改也不会引起组件渲染 createRef 与 useRef 的区别: createRef 每次渲染都会返回一个新的引用,而 useRef 每次都会返回相同的引用。 forwardRef是用来解决HOC组件传递ref的问题的 */
             const TestRef = forwardRef((props, ref) => {
               useImperativeHandle(ref, () => ({
                  open() { alert("ref") }
                 }))
                  return ( <div>useImperativeHandle 穿透 ref的使用</div> ) })
                  // 等同于 => // useImperativeHandle(ref,createHandle,[deps])可以自定义暴露给父组件的实例值。如果不使用,
                   // 父组件的 ref(chidlRef) 访问不到任何值(childRef.current==null)
                   // useImperativeHandle应该与forwradRef搭配使用
                   // React.forwardRef会创建一个React组件,这个组件能够将其接受的ref属性转发到其组件树下的另一个组件中。
                  // React.forward接受渲染函数作为参数,React将使用prop和ref作为参数来调用此函数 
const ChildComponent = (props, ref) => { useImperativeHandle(ref, () => ({ open() { alert("ref") } })); return <h3>useImperativeHandle 穿透 ref的使用</h3>; }; const TestRef1 = forwardRef(ChildComponent); function TimeTest(){ const [count, setCount] = useState(0); const preCount = usePreVal(count)
                  // 使用自定hook
const doubleCount = useMemo(() => { return 2 * count; }, [count]); const timerID = useRef(); useEffect(() => { timerID.current = setInterval(()=>{ setCount(count => count + 1); }, 1000); }, []); useEffect(()=>{ if(count > 10){ console.log('大于10定时器不再走了') clearInterval(timerID.current); } }); return ( <> <button ref={timerID} onClick={() => {setCount(count + 1)}}> Count: {count} === double: {doubleCount} === preCount:{preCount} </button> </> ); }
// 使用 const preState = usePreVal(state) 获取上一个值 // useRef 不仅仅是用来管理 DOM ref 的,它还相当于 this , 可以存放任何变量
function usePreVal(state){ const ref = useRef(); useEffect(() => { ref.current = state }) return ref.current }
                   // 自定义hooks,用use开头命名,封装重复使用的代码,在多个场景下使用,提高代码的复用性
                   // react hook监听窗口大小
                   function useChangeSize(){
                     const win = {
                       width: document.documentElement.clientWidth,
                      height: document.documentElement.clientHeight
                     }
                     const [size, setSize] = useState(win)
                     // useCallback缓存方法 [] 只执行一次
                     const onResize = useCallback(()=>{
                        console.log('111')
                         setSize(win)
                      },[])
                      useEffect(() => {
                         window.addEventListener('resize',onResize)
                          // 销毁时
                          return () => { window.removeEventListener('resize',onResize) } },
                          [onResize])
                          return size }
                          export default HookComp

 /** * 复用一个有状态的组件引发的思考:                           

 * * HOC【高阶组件: 一个函数接受一个组件作为参数,经过一系列加工后,最后返回一个新的组件】使用的问题: *  嵌套地狱,每一次HOC调用都会产生一个组件实例 可以使用类装饰器缓解组件嵌套带来的可维护性问题,

* 但装饰器本质上还是HOC 包裹太多层级之后,可能会带来props属性的覆盖问题 Render Props【渲染属性】:

*  数据流向更直观了,子孙组件可以很明确地看到数据来源 但本质上Render Props是基于闭包实现的,

* 大量地用于组件的复用将不可避免地引入了callback hell问题 丢失了组件的上下文,                            

* 因此没有this.props属性,不能像HOC那样访问this.props.children                            

* 涉及优化对比 函数组件 hook 和 class 【类】组件、函数组件 useCallback 和 useMemo 区别                           

 *  useCallback(fn, inputs) is equivalent to useMemo(() => fn, inputs)                            

* useCallback和useMemo的参数跟useEffect一致                            

* useMemo和useCallback都会在组件第一次渲染的时候执行,                            

* 之后会在其依赖的变量发生改变时再次执行;并且这两个hooks都返回缓存的值,                            

* useMemo返回缓存的变量,useCallback返回缓存的函数。                           

* 唯一的区别是:useCallback 不会执行第一个参数函数,                            

* 而是将它返回给你,而 useMemo 会执行第一个函数并且将函数执行结果返回给你 demo: useMemo 的用法                            *  export default function WithMemo() { const [count, setCount] = useState(1);                            * const [val, setValue] = useState(”);                            * const countSum = useMemo(() => { console.log(‘compute’);                            * let sum = 0; for (let i = 0; i < count * 100; i++) { sum += i; } return sum; },                            *  [count]);                            * return <div> <h4>{count}-{countSum}</h4> {val}                            *  <div> <button onClick={                            * () => setCount(count + 1)}>+c1</button>                            * <input value={val}                            * onChange={event => setValue(event.target.value)}/>                            * </div> </div>; }                            * demo: useCallback 的用法                            *  function Parent() { const [count, setCount] = useState(0); const [val, setVal] = useState(”);                            *  const callback = useCallback(() => { return count; },                            * [count]); return <div> <p>{count}</p> <Child callback={callback}/>                            * <div> <input value={val} onChange={event => setVal(event.target.value)}/>                            *  <button onClick={() => setCount(count + 1)}>add</button>                            * </div> </div>;                            * } function Child({ callback })                            * { const [count, setCount] = useState(() => callback()); useEffect(() => { setCount(callback()); },                            * [callback]); return <div> {count} </div> }                           

 * 使用场景是:有一个父组件,其中包含子组件,子组件接收一个函数作为props;通常而言,                            

* 如果父组件更新了,子组件也会执行更新;但是大多数场景下,更新是没有必要的,                            

* 我们可以借助useCallback来返回函数,                            

* 然后把这个函数作为props传递给子组件;这样,子组件就能避免不必要的更新 所有依赖本地状态或props来创建函数,                            

* 需要使用到缓存函数的地方,都是useCallback的应用场景 和 函数组件 class【类】 组件对比 * import React, { PureComponent } from ‘react’ * import React, { Component } from ‘react’ * React.memo(comp) 是React v16.6引进来的新属性 * React.memo会返回一个纯化(purified)的组件MemoFuncComponent * 当组件的参数props和状态state发生改变时,React将会检查前一个状态和参数是否和下一个状态和参数是否相同,如果相同,组件将不会被渲染,如果不同,组件将会被重新渲染 * 它的作用和React.PureComponent类似,是用来控制函数组件的重新渲染的。React.memo(…) 其实就是函数组件的 React.PureComponent * * demo: * class: Component 需要判断何时渲染 何时不会进行组件的重新渲染 * shouldComponentUpdate(nextProps, nextState) { if (this.state.count === nextState.count) { return false } return true } * const Funcomponent = ()=> { return ( <div> Hiya!! I am a Funtional component </div> ) } const MemodFuncComponent = React.memo(FunComponent) React.PureComponent是给ES6的类组件使用的 React.memo(comp)是给函数组件使用的 React.PureComponent减少ES6的类组件的无用渲染 React.memo(comp)减少函数组件的无用渲染 继承PureComponent时,不能再重写shouldComponentUpdate useEffect、useMemo、useCallback都是自带闭包的。每一次组件的渲染,其都会捕获当前组件函数上下文中的状态(state, props), 所以每一次这三种hooks的执行,反映的也都是当前的状态,你无法使用它们来捕获上一次的状态。 对于这种情况,应该使用ref来访问。 依赖为 [] 时: re-render 不会重新执行 effect 函数 没有依赖:re-render 会重新执行 effect 函数 useLayoutEffect 和 componentDidMount 和 componentDidUpdate 触发时机一致(都在在DOM修改后且浏览器渲染之前); useLayoutEffect 要比 useEffect 更早的触发执行; useLayoutEffect 会阻塞浏览器渲染,切记执行同步的耗时操作 关于class 组件的 createRef class RefTest extends React.Component{ constructor(props){ super(props); this.myRef = React.createRef(); } componentDidMount(){ console.log(this.myRef.current); } render(){ return <input ref={this.myRef}/> } } * */ // React 组件种类  // 1 Es5原生方式React.createClass定义的组件 // const Eg1 = React.createClass({ // getInitialState:function(){ // return { // name:’react’ // }; // }, // render:function(){ // return <div onClick={this._ClickEvent}>{ this.state.name }</div> // }, // _ClickEvent:function(){ // console.log(`事件`) // } // })  // 2 无状态组件 【功能组件】 function Eg2(props){ return <div>{ props.name }</div> } // 3 类组件 【有状态组件 or 容器组件】                            class Eg3 extends Component { constructor(props){ super(props)                             this.state = { } }                             render() { return ( <div> </div> ) } }                            // 4 渲染组件 与 无状态组件类似                            const Eg4 = (props) => { return ( <div>{ props.name }</div> ) }                            // 5 高阶组件 HOC 【不要在 render 方法中使用 HOC】 // 一个高阶组件是一个函数,它输入一个组件,然后返回一个新的组件 const EnhancedComponent = higherOrderComponent(WrappedComponent);                            function Eg5(WrappedComponent) {                              // HOC设置显示名称 Eg5.displayName = `Eg5(${getDisplayName(WrappedComponent)})`;                              return class extends React.Component { componentDidUpdate(prevProps)                               { console.log(‘Current props: ‘, this.props); console.log(‘Previous props: ‘, prevProps); }                               render() { return <WrappedComponent {…this.props} />; } } }                                // HOC设置显示名称                                function getDisplayName(WrappedComponent)                                { return WrappedComponent.displayName || WrappedComponent.name || “Component”; }                                // 6 function + hook 组件                                

*React Fiber 【Fiber 架构就是用 异步的方式解决旧版本 同步递归导致的性能问题】                                 

* 旧版 React 通过递归的方式进行渲染,使用的是 【JS引擎自身的函数调用栈】,                                 

* 它会一直执行到栈空为止。 Fiber实现了自己的【组件调用栈】,                                 

* 它以链表的形式遍历组件树,可以灵活的暂停、继续和丢弃执行的任务。                                 

* 实现方式是使用了浏览器的 requestIdleCallback                                

 * 这一 API: 将 计算任务 分给成一个个小任务,分批完成,在完成一个小任务后,                                 

* 将控制权还给浏览器,让浏览器利用间隙进行 UI 渲染 React Fiber                                 

* 出现的背景: 当页面元素很多,且需要频繁刷新的场景下,浏览器页面会出现卡顿现象,                                

 * 原因是因为 计算任务 持续占据着主线程,                                 

* 从而阻塞了 UI 渲染 React 框架内部的运作可以分为 3 层 Virtual DOM 层 –>                                 

* 描述页面长什么样 Reconciler 层 –>                                

 * 负责调用组件生命周期方法,进行 Diff 运算等。 Renderer 层 –>                                

 * 根据不同的平台,渲染出相应的页面,比较常见的是 ReactDOM 和 ReactNative                                 

* * * Fiber 其实指的是一种数据结构,它可以用一个纯 JS 对象来表示

* * * const fiber = { stateNode, // 节点实例 child, // 子节点 sibling,                                 

* // 兄弟节点 return, // 父节点 }                                 

*  Fiber Reconciler 的 2 个阶段: 阶段一,生成 Fiber 树【本质来说是一个链表】,                                

 * 得出需要更新的节点信息。这一步是一个渐进的过程,可以被打断。                                 

* 【让优先级更高的任务先执行,从框架层面大大降低了页面掉帧的概率】                                

 * 阶段二,将需要更新的节点一次过批量更新,这个过程不能被打断 阶段一有两颗树,  

* Virtual DOM 树和 Fiber 树,Fiber 树是在 Virtual DOM 树的基础上通过额外信息生成的。                                

 * 它每生成一个新节点,就会将控制权还给浏览器,如果浏览器没有更高级别的任务要执行,                                 

* 则继续构建;反之则会丢弃 正在生成的 Fiber 树,                                 

* 等空闲的时候再重新执行一遍 【V16版本之前】栈调和(Stack reconciler) –>                                

 * 【V16版本之后】 Fiber reconciler React diff 将传统 diff 算法的复杂度 O(n^3)                                

 * 复杂度的问题转换成 O(n) 复杂度的问题 React Diff三大策略:                                 

* 【将 Virtual DOM 树转换成 actual DOM 树的最少操作的过程称为 协调(Reconciliaton)】

* 1.tree diff: 【Web UI中DOM节点跨层级的移动操作特别少,可以忽略不计】 React对 Virtual DOM 树进行层级控制,只会对相同层级的DOM节点进行比较,即同一个父元素下的所有子节点, 当发现节点已经不存在了,则会删除掉该节点下所有的子节点,不会再进行比较。 这样只需要对DOM树进行一次遍历,就可以完成整个树的比较。复杂度变为O(n); 2.component diff: 【拥有相同类的两个组件 生成相似的树形结构,拥有不同类的两个组件 生成不同的树形结构】 3.element diff: 【对于同一层级的一组子节点,通过唯一id区分】                                 

*  当节点属于同一层级:插入、移动、删除 【设置唯一 key的策略】 */

本文来自投稿,不代表PmTemple立场,如若转载,请注明出处:

。如有涉及侵权行为,请发送相关证明材料至邮箱admin@pmtemple.com

发表评论

登录后才能评论
分享本页
返回顶部