1 2 3 4

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名字都会改成相同的值,实现了数据共享。

 

posted @ 2020-08-25 10:38  红鲤鱼与LV  阅读(588)  评论(0编辑  收藏  举报