zoukankan      html  css  js  c++  java
  • reack hook使用详解

    1.为什么使用hook

    我们经常维护一些组件,组件起初很简单,但是逐渐会被状态逻辑和副作用充斥。每个生命周期常常包含一些不相关的逻辑。例如,组件常常在 componentDidMount 和 componentDidUpdate 中获取数据。但是,同一个 componentDidMount 中可能也包含很多其它的逻辑,如设置事件监听,而之后需在 componentWillUnmount 中清除。相互关联且需要对照修改的代码被进行了拆分,而完全不相关的代码却在同一个方法中组合在一起。如此很容易产生 bug,并且导致逻辑不一致。

    为了解决这个问题,Hook 将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据),而并非强制按照生命周期划分。你还可以使用 reducer 来管理组件的内部状态,使其更加可预测。

    2.hook使用规则

    • 只能在react的函数组件使用调用hook
    • 需要在组件顶层调用,不能在判断啊,循环里调用,因为hook是按顺序执行的,加入放在判断里,第一个调用了,第二次没调用,后面的hook调用提前执行,会导致bug。

    3.useState(修改state)

    import react,{useState, useEffect} from 'react'
    
    export default function() {
      const [count,setCount] = useState(0)
      const [arr,setArr] = useState([])
      const [obj,setObj] = useState({name:'a'})
      const [func,setFunc] = useState(()=>{
        return 1//函数类型接受的是这个函数的返回值
      })
    
      useEffect(()=>{
        document.title = `${count}times`
      })
    
      return (
        <>
          <div>{count}</div>
          <button onClick={()=>{
            setCount(count+1)
          }}>click</button>
          <div>{arr.length}</div>
          <button onClick={()=>{
            setArr(()=>{
              arr.push(a)
              return [...arr]//必须返回新数组,否则没变化
            })
          }}>clickArr</button>
          <div>{obj.name}</div>
          <button onClick={()=>{
            setObj({
              ...obj,
              age:18
            })
            // setObj(Object.assign(obj,{name:'b'}))//此方法不可以,必须是返回一个新对象
          }}>clickObj</button>
          <div>{func}</div>
    </> ); }

    5.useEffect(componentDidMount,componentDidUpdate和componentWIllUnmount)

    useEffect取代了class组件中的componentDidMount,componentDidUpdate和componentWIllUnmount。可以在useEffect里执行一些副作用(Dom操作、数据请求、组件更新)。useEffect是无阻塞的更新,比如请求数据我们可以放在componentDidMount和componentDidUpdate中,即组件挂载之后或更新之后,而不是挂载之前,如果在挂载之前,数据请求失败会导致组件无法挂载,影响视图渲染,界面无法正常显示。

    useEffect是个方法,有两个参数,第一个参数是回调函数,第二个参数是数组,

    • 第二个参数如果不写,只要状态改变都会执行。
    • 第二个参数是个空数组时,不管哪个状态改变都不执行,只在组件初始时执行一次。
    • 当第一个回调函数中有return返回值时,表示componentWIllUnmount时执行

    import styles from './index.css';
    import react,{useState, useEffect} from 'react'
    
    export default function() {
      const [count,setCount] = useState(0)
    
      useEffect(()=>{
        console.log(11)
        return ()=>{
          console.log('componentWillUnmount')//组件componentWillUnmount时执行
        }
      })
    
      useEffect(()=>{
        console.log(22)
      },[])//只有组件初始时执行
    
      useEffect(()=>{
        console.log(count)
      },[count])//只有count改变时执行
    
      return (
        <>
          <div>useEffect</div>
        </>
      );
    }

    4.自定义hook

    通过自定义 Hook,可以将组件逻辑提取到可重用的函数中。

    注:

    • 自定义 Hook 必须以 “use” 开头
    • 可以使用hook(useState)

     

    5.useRef(ref)

    useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。

    用途:(1)获取某个dom元素,或者dom元素(如input)的值

             (2)保存变量

    import styles from './index.css';
    import react,{useState, useEffect, useRef} from 'react'
    
    export default function() {
      const inputEl = useRef(null)
      const save = useRef({value:'123'})
    
      return (
        <>
          <input ref = {inputEl} type='text'/>
          <br/>
          <button onClick={()=>{
            console.log(inputEl.current)//获取了input框这个dom元素
            console.log(inputEl.current.value)//获取这个dom元素的值
            console.log(save)
            inputEl.current.focus();//聚焦input框
          }}>click</button>
        </>
      );
    }

    6.useContext(父组件给子组件传值或方法)

    使用:首先通过createContext创建一个父组件容器,在子组件通过useContext接收父组件传过来的值

    import styles from './index.css';
    import react,{useState, useEffect, useRef,createContext, useContext} from 'react'
    const MyContext = createContext()//创建一个容器组件(第一步)
    
    const ChildContext = () => {
      let count = useContext(MyContext)//接受不了父组件传的值(第三步)
      return (
      <h3>我是子组件{count}</h3>
      )
    }
    
    export default function() {
      const [count,setCount] = useState(0)
      const inputEl = useRef(null)
    
      return (
        <>
          <MyContext.Provider value={count}>{/* (第二步)建立一个容器组件 ,通过value传需要给子虚=组件传的值*/}
            <ChildContext></ChildContext>{/* 子组件放在容器组件里 */}
          </MyContext.Provider> 
          <input ref = {inputEl} type='text'/>
          <br/>
          <button onClick={()=>{
            inputEl.current.focus();//聚焦input框
            setCount(inputEl.current.value)
          }}>click</button>
        </>
      );
    }

    7.useMemo(shouldComponentUpdate)

    作用:与shouldComponentUpdate类似作用,在渲染过程中避免重复渲染的问题。控制组件是否更新

    返回值:返回更新的内容

    原理:底层用的是memoization(js的一种缓存技术)来提高性能,用到了缓存技术

    用法:其是一个函数,有两个参数,第一个参数是回调函数,第二个是数组。第二个参数用法同useEffect

    区别:与useEffect执行的时间不同,useEffect是在componentDidMount以后(即组件挂载之后)执行,而useMemo是在渲染过程中执行。useEffect是控制函数是否更新执行,而useMemo是控制组件是否更新执行。

    import styles from './index.css';
    import react,{useState, useMemo, useEffect} from 'react'
    
    export default function() {
      let [count ,setCount] = useState(0)
      let [num ,setNum ] = useState(0)
    
      let res = useMemo(()=>{
        return count
        //retutn {count,num}
      },[])
    
      return (
        <>
          <div>{count}----</div>
          {/* <div>{res.count}----</div> */}
          <button onClick={()=>{
            setCount(count+1)
          }}>countClick</button>
          <button onClick={()=>{
            setNum(num+1)
          }}>numClick</button>
        </>
      );
    }

    8.useCallback(控制组件什么时候更新)

    作用:避免组件重复渲染,提高性能,可以控制组件什么时候需要更新

    返回:返回一个函数callback,可以直接callback()执行

    参数:第一个参数是一个回调函数,第二个参数同useEffect的第二个参数

    import styles from './index.css';
    import react,{useContext,useState, useCallback} from 'react'
    
    export default function() {
      let [count ,setCount] = useState(0)
    
      let callback = useCallback(()=>{
        console.log(count)
        return count
      },[])//当写空数组时,是由第一次会执行更新,之后更新组件的时候也会执行该函数,但执行的是缓存中的,即count的值永远打印的是第一次的
    
      return (
        <>
          <div>{count}----</div>
          <div>{callback()}callback----</div>
          <button onClick={()=>{
            setCount(count+1)
          }}>countClick</button>
        </>
      );
    }

     复杂版:

    import styles from './index.css';
    import react,{useContext,useState, useCallback} from 'react'
    
    export default function() {
      let [count ,setCount] = useState(0)
      let [num ,setNum] = useState(0)
    
      let callback = useCallback(()=>{
        console.log(count)
        return `count:${count}---num:${num}`
      },[count])//当写空数组时,是由第一次会执行更新,之后更新组件的时候也会执行该函数,但执行的是缓存中的,即count的值永远打印的是第一次的
    
      return (
        <>
          <div>{count}----</div>
          <div>{callback()}callback----</div>
          <button onClick={()=>{
            setCount(count+1)
          }}>countClick</button>
          <button onClick={()=>{
            setNum(num+1)
          }}>countNum</button>
        </>
      );
    }

    9.useImperativeHandle(自定义暴露给父组件的实力值)

    作用:可以让你在使用ref时自定义暴露给父组件的实例值。在大多数情况下,应当避免使用ref这样的命令式代码。useImperativeHandle应当与forwardRef一起。

    格式:useImperativeHandle(ref(传递来的),()=>{},[])

    参数:3个。第一个是父组件传过来的ref的名字。第二个参数:函数,第三个参数:数组(用于监控hook,同useEffect)

    forward使用(配合useRef使用):

    用于封装组件,从父组件传过来的ref,传到某个元素身上,父组件就能拿到子组件某个dom

    import styles from './index.css';
    import react,{forwardRef} from 'react'
    
    const Forward = forwardRef((props,ref)=>{
      return (
        <>
          <h3 ref={ref}>h3</h3>
          <h4>h4</h4>
        </>
      )
    })
    
    export default () => {
      const el = useRef(null)
      return (
        <>
          <Forward ref={el}/>
          <button onClick={()=>{
            console.log(el.current)
          }}>获取子组件ref</button>
        </>
      )
    }

     简易版案例:

    import {forwardRef, useImperativeHandle,useRef} from 'react'
    
    //子组件
    const Imperative = forwardRef((props,refa)=>{
        const inputRef  = useRef(null)
     
        useImperativeHandle(refa,()=>{
            return {
                name:'zkq',//自定义暴露属性
                focus:()=>{
                    inputRef.current.focus()//把子组件的input的焦点暴露给父组件
                },
            }
        })
        return (
            <>
                <input type="text" ref={inputRef}/>  
            </>
        )
    })
    
    //父组件
    export default () =>{
        const el = useRef()
        return (
            <>
                <Imperative ref={el}/>
                <button onClick={()=>{
                    console.log(el)
                    el.current.focus()
                }}>获取子组件的自定义方法或者属性</button>
            </>
        )
    }

     复杂版案例:

    import {forwardRef, useImperativeHandle,useRef,useState} from 'react'
    
    //子组件
    const Imperative = forwardRef((props,refa)=>{
        const [count,setCount] = useState(0)
        const [num,setNUm] = useState(0)
        const inputRef  = useRef(null)
     
        useImperativeHandle(refa,()=>{
            return {
                name:'zkq',//自定义暴露属性
                focus:()=>{
                    inputRef.current.focus()//把子组件的input的焦点暴露给父组件
                },
                count,
                num
            }
        },[count])
        return (
            <>
                <h3>count:{count}</h3>
                <h3>num:{num}</h3>
                <input type="text" ref={inputRef}/>
                <button onClick={()=>{
                    setCount(count+1)
                }}>setCount</button>
                <button onClick={()=>{
                    setNUm(num+1)
                }}>setCount</button>    
            </>
        )
    })
    
    //父组件
    export default () =>{
        const el = useRef()
        return (
            <>
                <Imperative ref={el}/>
                <button onClick={()=>{
                    console.log(el)
                    el.current.focus()
                }}>获取子组件的自定义方法或者属性</button>
            </>
        )
    }

    10.useLayoutEffect

    与useEffect作用一样,在组件生成过程中,可以做一些操作

    不同:

    • 执行的时间不同,useEffect是在componentDIdMount以后执行,useLayoutEffect在浏览器执行绘制之前执行(会阻塞组件挂载,慎用)
    import React,{useLayoutEffect,useEffect} from 'react'
    
    export default ()=>{
        useEffect(()=>{
            console.log('useEffect')
            return ()=>{
                console.log('useEffect-return')
            }
        })
    
        useLayoutEffect(()=>{
            console.log('useLayoutEffect')
            return ()=>{
                console.log('useLayoutEffect-return')
            }
        })
        return (
            <>
              <h1>useLayoutEffect</h1>
            </>
        )
    }

    11.useReducer (reducer)

    useReducer和redux的Reducer是一样的,说白了Reducer就是个函数。

    参数:useReducer()是个函数,有三个参数,第一个:reducer,第二个:初始值,第三个:init。

    返回值:返回数组,第一个是state,第二个是dispatch

    用法:const [state,dispatch] = useReducer(reducer,初始值)

    基本用法:

    import React,{useReducer,useEffect, useRef} from 'react'
    
    export default ()=>{
        const [state,dispatch] = useReducer((state,action)=>{
            switch(action.type){
                case 'setName':
                    return {
                        ...state,
                        name:action.name
                    }
                case 'setAge':
                    return {
                        ...state,
                        age:action.age
                    }
                default:
                    return state
            }
        },{name:'abc',age:18})
        return (
            <>
              <h1>{state.name}-----{state.age}</h1>
              <button onClick={()=>{
                  dispatch({
                      type:'setName',
                      name:'123'
                  })
              }}>setName</button>
              <button onClick={()=>{
                  dispatch({
                      type:'setAge',
                      age:'23'
                  })
              }}>setAge</button>
            </>
        )
    }

    与useContext结合使用,效果和redux相同

    下面我们简单模拟下redux:需要四个文件(index.js,text1.js,text2.js,reducer.js)

    reducer.js:

    import React,{createContext,useReducer} from 'react'
    export const MyContext = createContext()
    
    const reducer = (state,action)=>{
        switch(action.type){
            case 'setName':
                return{
                    ...state,
                    name:action.name
                }
            case 'setAge':
                return {
                    ...state,
                    age:action.age
                }
            default:
                return state
        }
    }
    
    const data = {
        name:'zhangsan',
        age:18
    }
    
    export const Reducer = (props)=>{
        let [state,dispatch] = useReducer(reducer,data)
    
        return (
            <MyContext.Provider value={{state,dispatch}}>
                {props.children}
            </MyContext.Provider>
        )
    }

    text1.js

    import React,{useContext} from 'react'
    import {MyContext} from './reducer'
    export default ()=>{
        let {state,dispatch} = useContext(MyContext)
    
        return (
            <>
                <h1>text1:名字{state.name},年龄{state.age}</h1>
                <button onClick={()=>{
                    dispatch({
                        type:'setName',
                        name:'Text1'
                    })
                }}>setName</button>
                <button onClick={()=>{
                    dispatch({
                        type:'setAge',
                        age:10
                    })
                }}>setAge</button>
            </>
        )
    }

    text2.js:

    import React,{useContext} from 'react'
    import {MyContext} from './reducer'
    export default ()=>{
        let {state,dispatch} = useContext(MyContext)
        return (
            <>
                <h1>text2:名字{state.name},年龄{state.age}</h1>
                <button onClick={()=>{
                    dispatch({
                        type:'setName',
                        name:'Text2'
                    })
                }}>setName</button>
                <button onClick={()=>{
                    dispatch({
                        type:'setAge',
                        age:20
                    })
                }}>setAge</button>
            </>
        )
    }

    index.js:

    import { Reducer } from './reducer';
    import Text1 from './test1'
    import Text2 from './test2'
    
    export default () => {
     
      return (
        <>
          <Reducer>//必须这么包裹写。
            <Text1/>
            <Text2/>
          </Reducer>
        </>
      )
    }

    效果如图:点击text1的setName,text1和text2名字都会改成相同的值,实现了数据共享。

     

  • 相关阅读:
    MYSQL索引
    列表里重复次数最多的元素
    python学习笔记
    Spark 扫描 HDFS lzo/gz/orc异常压缩文件
    Yarn RM写ZNode超数据量限制bug修复
    Spark HistoryServer日志解析&清理异常
    【Yarn源码分析】Container启动流程源码分析
    Yarn NodeManager总体架构
    【Yarn源码分析】ApplicationMaster源码分析
    【Yarn源码分析】FairScheduler资源调度
  • 原文地址:https://www.cnblogs.com/kaiqinzhang/p/13536373.html
Copyright © 2011-2022 走看看