美文网首页
react hook

react hook

作者: liuxinya | 来源:发表于2020-06-10 20:40 被阅读0次

    React Hooks

    • 每次render 都有独立的变量和Effects

    1. useState

    • vue - data
    data() {
        return {
            count: 0
        }
    }
    
    • react类组件里面的 state
    interface CounterStateObj {
        count: number
    }
    export class Counter extends React.Component <any, CounterStateObj>{
        constructor(props) {
            super(props);
            this.state = {
                count: 0
            };
        }
    }
    
    • hook
    function Counter() {
        const [count, setCount] = useState<number>(0);
        const log = () => {
            setTimeout(() => {
                console.log('2秒后', count)
            }, 2000)
            console.log(count)
        }
        const clickHandler = () => {
            log()
            setTest(count + 1)
        }
        return (
            <div>
                <p>clicked {count} times</p>
                <button onClick={clickHandler}>Click me</button>
            </div>
        );
    }
    
    • Capture Value
    页面 log函数
    1 0
    2 1
    3 2
    ... ...
    • 类比照相机对着一个杯子拍照,第二张杯子碎了,但第一张照片永远是好的(每次setState就是新拍了一张照片,它不会影响前一个capture的状态)
    function Example2() {
        const [count, setCount] = useState(0);
    
        const handleClick = () => {
            setTimeout(() => {
                setCount(count + 1);
            }, 3000);
        };
    
        return (
            <div>
                <p>{count}</p>
                <button onClick={() => setCount(count + 1)}>
                    setCount
                </button>
                <button onClick={handleClick}>
                    Delay setCount
                </button>
            </div>
        );
    }
    

    操作 先点击第二个按钮, 然后在3秒内连续点击两次第一个按钮

    页面结果 0 -> 1 -> 2 -> 1

    问题 --- 接着再重复操作呢?

    • 拿到最新的值
    setCount(data => data + 1)
    

    2. useRef

    • useRef 返回的 ref 对象在组件的整个生命周期内保持不变,也就是说每次重新渲染函数组件时,返回的ref 对象都是同一个(使用 React.createRef ,每次重新渲染组件都会重新创建 ref)
    function Counter() {
        const count = useRef<number>(0);
        const domRef = useRef<HTMLDivElement>()
        const clickHandler = () => {
            count.current++
        }
        return (
            <div>
                <p>You clicked {count.current} times</p>
                <div ref='domRef'></div>
                <button onClick={clickHandler}>Click me</button>
            </div>
        );
    }
    
    • 访问dom
      • vue
        <div ref='aa'></div>
        this.$refs['aa']
      
      • react class
      class MyComponent extends React.Component {
          constructor(props) {
              super(props);
              this.myRef = React.createRef();
          }
          render() {
              return <div ref={this.myRef} />;
          }
      }
      

    3. useCallback

    • 依赖不变,返回的是同一个函数
    const [contentClassName, setContentClassName] = useState<string>('')
    const resizeHandler = useCallback(() => {
             if (contentClassName) {
                 setH(contentClassName)
             }
     }, [contentClassName])
    
        useEffect(() => {
            let event: UEventEmitter = Ioc(UEventEmitter)
            event.on('fullScreenChange', () => {
                resizeHandler()
            })
            window.addEventListener('resize', resizeHandler)
            return () => {
                window.removeEventListener('resize', resizeHandler)
                event.delete('fullScreenChange')
            }
        }, [contentClassName, resizeHandler])
    

    4. useEffect

    • 只要状态更新,它就会根据传入的依赖项决定是否执行, 可以拿到最新的状态
    • react 类组件 里面的 componentDidMount + componentDidUpdate
    • Vue created + updated
    • 与 componentDidMount 或 componentDidUpdate 不同, useEffect 调度的 effect 不会阻塞浏览器更新屏幕,这让你的应用看起来响应更快
    // 变化执行
    useEffect(() => {
    
    })
    // 变化不执行
    useEffect(() => {
    
    }, [])
    
    // 只有 test 变化才会执行
    useEffect(() => {
      return () => {}
    }, [test])
    

    5. useMemo

    • 类比 Vue 的 computed
    • 适合大量计算,缓存值,依赖不变不会重新更新
    const [value, setValue] = useState(0);
    const increase = useMemo(() => {
        if(value > 2) return value + 1;
    }, [value]);
    
    • React.memo()React.PureComponent区别?

    React.memo()适用函数组件,仅仅浅比较props
    React.PureComponent类组件,浅比较props和state,根据结果决定是否重新渲染组件

    function Parent({ a, b }) {
      // Only re-rendered if `a` changes:
      const child1 = useMemo(() => <Child1 a={a} />, [a]);
      // Only re-rendered if `b` changes:
      const child2 = useMemo(() => <Child2 b={b} />, [b]);
      return (
        <>
          {child1}
          {child2}
        </>
      )
    }
    

    6. useContext

    • 获取context值

    7. useReducer

    • 像 redux一样管理数据和行为

    8. useLayoutEffect

    • 浏览器 layout 之后,painting 之前执行
    • 可以获取元素样式

    react hooks详解实战


    轮子

    1.实现类似类组件的生命周期

    • componentDidMount
    export function useOnMount(fn: () => void, destoryCallBack?: () => void) {
        useEffect(() => {
            fn()
            if (destoryCallBack) {
                return () => {
                    destoryCallBack()
                }
            }
        // eslint-disable-next-line
        }, [])
    }
    
    • componentDidUpdate
    export function useOnUpdate(fn: () => void, dep?: any[]) {
        const ref = useRef({ fn, mounted: false })
        ref.current.fn = fn
      
        useEffect(() => {
            // 首次渲染不执行
            if (!ref.current.mounted) {
                ref.current.mounted = true
            } else {
                ref.current.fn()
            }
        // eslint-disable-next-line
        }, dep)
    }
    
    • forceUpdate
    export function useForceUpdate() {
        const [, setValue] = useState(0)
        return useCallback(() => {
            // 递增state值,强制React进行重新渲染
            setValue(val => (val + 1) % (Number.MAX_SAFE_INTEGER - 1))
          }, [])
    }
    

    2.实现多语言

    • 当语言变化的时候 触发一次render
    • 根据当前语言返回对应的值
    import { store } from '../store/redux';   // redux
    import { sysLanguage } from '../config/langulate';  // 语言配置文件
    import { useState, useEffect } from 'react';  
    import { LangValue } from '../interface/common';  // 接口
    export function useLanguage() {
        const [lang, setLang] = useState<LangValue>(store.getState().user.lang)
        useEffect(() => {
            let unsubscribe = store.subscribe(() => {
                setLang(store.getState().user.lang)
            })
            return () => {
                unsubscribe()
            }
        // eslint-disable-next-line
        }, [])
        return [(cn: string) => {
                if (cn && sysLanguage[lang] && sysLanguage[lang][cn]) {
                    return sysLanguage[lang][cn]
                } else {
                    return cn || ''
                }
            }
        ]
    }
    

    优化

      1. 大量计算 适用useMemo,
      1. 为避免子组件的不必要渲染,useMemo + React.memo() 或 React.PureComponent

    例1

    function Test() {
        console.log('test render')
        return (
            <div className="test">
                呵呵呵
            </div>
        )
    }
    
    export function App() {
        const [test, setTest] = useState(1)
        console.log('app render')
        return (
            <div className='app-container' onClick={() => setTest(test + 1)}>
                { test }
                <Test/>
            </div>
        )
    }
    

    每次点击 App 和 Test 各重新渲染一次

    例1优化

    function TestTem() {
        console.log('test render')
        return (
            <div className="test">
                呵呵呵
            </div>
        )
    }
    const Test = React.memo(TestTem)
    export function App() {
        const [test, setTest] = useState(1)
        console.log('app render')
        return (
            <div className='app-container' onClick={() => setTest(test + 1)}>
                { test }
                <Test/>
            </div>
        )
    }
    

    每次点击 只有 App 重新渲染

    例2

    type ConfigObj = {
        name: number
    }
    function TestTem(props: {
        config: ConfigObj
    }) {
        console.log('test render')
        return (
            <div className="test">
                呵呵呵
                {props.config.name}
            </div>
        )
    }
    const Test = React.memo(TestTem)
    export function App() {
        const [test, setTest] = useState(1)
        const [nameData, setNameData] = useState(1)
        const config: ConfigObj = {
            name: nameData
        }
        const clickHandler = () => {
            setTest(test + 1)
            // setNameData(nameData + 2)
        }
        console.log('app render')
        return (
            <div className='app-container' onClick={clickHandler}>
                { test }
                <Test config={config}/>
            </div>
        )
    }
    

    每次点击 App 和 Test 各执行一次

    例2优化

    type ConfigObj = {
        name: number
    }
    function TestTem(props: {
        config: ConfigObj
    }) {
        console.log('test render')
        return (
            <div className="test">
                呵呵呵
                {props.config.name}
            </div>
        )
    }
    const Test = React.memo(TestTem)
    export function App() {
        const [test, setTest] = useState(1)
        const [nameData, setNameData] = useState(1)
        // 优化
        const config: ConfigObj = useMemo(() => {
            return {
                name: nameData
            }
        }, [nameData])
        const clickHandler = () => {
            setTest(test + 1)
            // setNameData(nameData + 2)  // 放开这句话 会让config 产生变化 Test会重新渲染
        }
        console.log('app render')
        return (
            <div className='app-container' onClick={clickHandler}>
                { test }
                <Test config={config}/>
            </div>
        )
    }
    
      1. 惰性创建昂贵的对象
        const aaa = () => {
            console.log('喀纳斯')
            return 1
        }
        // 每次render,都会执行一遍
        const [test, setTest] = useState<number>(aaa())
        // 只会被执行一次 
        const [test, setTest] = useState<number>(() => aaa())
    
      1. 在useEffect里面及时解绑事件
        const resizeHandler = useCallback(() => {
                if (contentClassName) {
                    setH(contentClassName)
                }
            }, [contentClassName])
        useEffect(() => {
            let event: UEventEmitter = Ioc(UEventEmitter)
            event.on('fullScreenChange', () => {
                resizeHandler()
            })
            window.addEventListener('resize', resizeHandler)
            return () => {
                window.removeEventListener('resize', resizeHandler)
                event.delete('fullScreenChange')
            }
        }, [contentClassName, resizeHandler])
    
      1. 通用逻辑抽取为自定义hook
      1. 复杂的操作和状态变更,适用useReducer
      1. 避免向下传递回调, 在大型的组件树中,我们推荐的替代方案是通过 context 用 useReducer往下传一个 dispatch 函数:
    const TodosDispatch = React.createContext(null);
    
    function TodosApp() {
      // 提示:`dispatch` 不会在重新渲染之间变化
      const [todos, dispatch] = useReducer(todosReducer);
    
      return (
        <TodosDispatch.Provider value={dispatch}>
          <DeepTree todos={todos} />
        </TodosDispatch.Provider>
      );
    }
    
    function DeepChild(props) {
      // 如果我们想要执行一个 action,我们可以从 context 中获取 dispatch。
      const dispatch = useContext(TodosDispatch);
    
      function handleClick() {
        dispatch({ type: 'add', text: 'hello' });
      }
    
      return (
        <button onClick={handleClick}>Add todo</button>
      );
    }
    
      1. 每次render 组件执行两次?
    export default function Test () {
        const [test, setTest] = useState(1)
        console.log('1111')
        return (
            <div>
                {test}
                <div onClick={() => setTest(test + 1)}>
                    23123123
                </div>
            </div>
        )
    }
    

    严格模式不能自动检测到你的副作用,但它可以帮助你发现它们,使它们更具确定性。通过故意重复调用以下函数来实现的该操作

    严格模式

      1. Hook 会因为在渲染时创建函数而变慢吗?

    不会

    官方回答

    来自知乎社区的回答

    ** 项目结构


    IRIS.png

    相关文章

      网友评论

          本文标题:react hook

          本文链接:https://www.haomeiwen.com/subject/nfaaahtx.html