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