1、什么是 hook?
react hook 是 react 16.8 推出的办法,可能让函数式组件像类式组件一样领有 state、ref、生命周期等属性。
2、为什么要呈现 hook?
函数式组件是全局当中一个一般函数,在非严格模式下 this 指向 window,然而 react 外部开启了严格模式,此时 this 指向 undefined,无奈像类式组件一样应用 state、ref,函数式组件定义的变量都是部分的,当组件进行更新时会从新定义,也无奈存储,所以在 hook 呈现之前,函数式组件有很大的局限性,通常状况下都会应用类式组件来进行代码的编写。
3、有哪些罕用的 hook?
(1) useState
使函数式组件也能保留状态的一个 hook,这个 hook 的入参是状态的初始值,返回值是一个数组,数组里第一个参数为状态的值,第二个参数为批改状态的办法。
// 初始化
const [count, setCount] = useState(0)
// 更新
setCount(count+1)
(2) useEffect
函数式组件用来模仿生命周期的 hook,能够模仿组件挂载实现、更新实现、行将卸载三个阶段,即 componentDidMount、componentDidUpdate、componentWillUnmount。
useEffect 的一个参数为函数,示意组件挂载、更新时执行的内容,在函数里再返回一个函数,示意组件行将卸载时调用的函数。
第二个参数为可选项,可传入数组,数组里能够为空,示意不依赖任何状态的变动,即只在组件行将挂载时执行,后续任何状态产生了变动,都不调用此 hook。数组里也能够定义一或多个状态,示意每次该状态变动时,都会执行此 hook。
useEffect(()=>{// 这样模仿的是 componentDidMount}, [])
useEffect(()=>{// 这样模仿的是 componentDidMount 以及当 count 发生变化时执行 componentDidUpdate}, [count])
useEffect(()=>{return ()=>{// 这样模仿的是 componentWillUnmount}
}, [])
(3) useContext
在没有 hook 之前,咱们通常都会通过 xxxContext.Provider 和 xxxContext.Consumer 的形式来传递和获取 context 的值,应用 hook 之后,传递 context 的形式不变,但子元素获取 context 的形式变得更加的简洁。
// 以前的定义形式
const CountContext = React.createContext()
<CountContext.Provider value={{count: 10}}>
<... 自定义的组件 >
</CountContext.Provider>
// 子元素
<CountContext.Consumer>
{value => { console.log(value.count) }} //10
</CountContext.Consumer>
// 应用 context 的获取形式
const countObj = useContext(CountContext)
console.log(countObj.count) // 10
(4) useRef
useRef 和类式组件中 createRef 用法比拟相似,返回一个 ref 对象,这个对象在函数的整个生命周期都不变,依据这个个性,有两种比拟常见的用法。
① 用于 dom 元素或者组件上,通过 current 属性能够获取到 dom 元素或者类式组件的实例对象。须要留神的是,无论是 useRef 还是 createRef 或者是回调模式、字符串模式的 ref,都是不能间接给函数式组件定义的,因为函数式组件的 this 指向 undefined,没有实例对象,只能通过 forwardRef 定义到函数式组件中的某个 dom 元素。
// 这样就将传递给函数式组件的 ref 绑定在了函数式组件外部的 input 标签上
import React, {useRef, forwardRef} from 'react'
// 应用函数表达式的形式定义了一个函数式组件
const InputCom = forwardRef((props, ref) => {return <input ref={ref}/>
})
export default function refDemo(){const comRef = useRef()
return(<div>
<InputCom ref={comRef}/>
</div>)
}
② 保留一个数据,该数据如果不手动批改,它在整个生命周期中都不变
const [count, setCount] = useState(0)
const prevCount = useState(count)
// 当 count 发生变化时,组件更新,对 count 的前一次数据进行保留
useEffect(()=>{prevCount.current = count}, [count])
(5) useReducer
useReducer 相当于是 useState 的升级版,作用与 useState 相似,都是用来保留状态,但它的不同点在于能够定义一个 reducer 的纯函数,来解决简单数据。
// 定义一个解决数据的 reducer 纯函数
function reducer(prevState, action){switch(action.type){
case 'increment':
return {...prevState, count: prevState.count + 1}
case 'decrement':
return {...prevState, count: prevState.count - 1}
default:
return prevState
}
}
// 初始化状态
const [count, dispatch] = useReducer(reducer, { count: 0})
// 批改状态,此时的批改须要派发一个 action,让传入的 reducer 函数进行解决
dispatch({type: 'increment'})
(6) useCallback
函数式组件中,每一次更新状态,自定义的函数都要进行从新的申明和定义,如果函数作为 props 传递给子组件,会造成子组件不必要的从新渲染,有时候子组件并没有应用到父组件发生变化的状态,此时能够应用 useCallback 来进行性能优化,它会为函数返回一个记忆的值,如果依赖的状态没有发生变化,那么则不会从新创立该函数,也就不会造成子组件不必要的从新渲染。
import React, {useState, useCallback, memo} from 'react'
const AddBtn = memo((props)=>{ // 应用函数表达式的形式定义了一个函数式组件
return<button onClick={props.increment}>+1</button>
})
export default function CallBackPerformance(){const [ count, setCount] = useState(0)
const [show, setShow] = useState(true)
const increment1 = () => {console.log('increment1 被调用了')
setCount(count+1)
}
const increment2 = useCallback(()=>{ // 应用了 useCallback 来优化的函数
console.log('increment2 被调用了')
setCount(count+1)
},[count])
return(<div>
<div> 以后计数:{count}</div>
<AddBtn increment={increment1} name="1"/>
<AddBtn increment={increment2} name="2"/>
<button onClick={e => setShow(!show)}> 切换 show</button>
</div>)
}
// 当 show 这个状态发生变化时,子组件 increment1 会从新渲染,increment2 不会从新渲染
(7) useMemo
useMemo 也是返回一个记忆的值,如果依赖的内容没有产生扭转的话,这个值也不会发生变化,useMemo 与 useCallback 的不同点在于 useMemo 须要在传入的函数里须要 return 一个值,这个值能够是对象、函数,格局如下。
useMemo(()=>{return { count}
}, [count])
// 应用 useCallback 时
const increment2 = useCallback(()=>{setCount(count+1)
},[count])
// 应用 useMemo 模仿 useCallback
const increment2 = useCallback(()=>{return ()=>{setCount(count+1)
}
},[count])
// useMemo 的利用场景,当要进行一些简单的计算时,// 计算的值没有发生变化,并不需要每一次更新都从新计算
import React, {useState, useMemo} from 'react'
const calculateNum = (count) => {console.log('total 从新计算了')
let total = 0
for(let i = 0; i <= count; i++){total += i}
return total
}
export default function ComplexUseMemo(){const [ count, setCount] = useState(10)
const [show, setShow] = useState(true)
const total = useMemo(()=>{return calculateNum(count)
}, [count])
return(<div>
<div>{total}</div>
<button onClick={e=>setCount(count+1)}>+1</button>
<button onClick={e=>setShow(!show)}> 切换 show</button>
</div>)
}
(8) useImperativeHandle
这个是与 forwardRef 配合来应用的,当咱们对函数式组件应用 forwardRef 将 ref 指定了 dom 元素之后,那就父组件就能够任意的操作指定的 dom 元素,应用 useImperativeHandle 就是为了管制这样的一种行为,指定父元素可操作的子元素的办法。
import React, {useRef, useImperativeHandle, forwardRef} from 'react'
const InputComp = forwardRef((props, ref)=>{const childInputRef = useRef()
useImperativeHandle(ref, ()=>({focus: ()=>{childInputRef.current.focus()
}
}), [childInputRef.current])
return<input ref={childInputRef}></input>
})
export default function ImperativeHookDemo() {const inputRef = useRef()
return(<div>
<InputComp ref={inputRef}/>
<button onClick={e=>inputRef.current.focus()}> 聚焦 </button>
</div>)
}
(9) useLayoutEffect
这个办法与 useEffect 相似,只是执行的程序稍有不同,useEffect 是在组件渲染绘制到屏幕上之后,useLayoutEffect 是 render 和绘制到屏幕之间。
4、如何自定义 hook?
hook 只能定义在函数式组件中,不能在一般函数中应用,如果咱们想要应用到下面的 hook 来封装一些办法供很多个组件调用,这时候就须要自定义 hook,自定义 hook 的命名就是在函数名前加 use,函数名由 saveInfo 改为 useSaveInfo 即可。