react-hooks思想和初衷,也是把组件,颗粒化,单元化,形成独立的渲染环境,减少渲染次数,优化性能。
useCallback
useContext
useEffect
useLayoutEffect
useMemo
useReducer
useRef
useState
数据存储,派发更新。
useState出现,使得react无状态组件能够像有状态组件一样,可以拥有自己state,useState的参数可以是一个具体的值,也可以是一个函数用于判断复杂的逻辑,函数返回作为初始值,usestate 返回一个数组,数组第一项用于读取此时的state值 ,第二项为派发数据更新,组件渲染的函数,函数的参数即是需要更新的值。useState和useReduce 作为能够触发组件重新渲染的hooks,我们在使用useState的时候要特别注意的是,useState派发更新函数的执行,就会让整个function组件从头到尾执行一次,所以需要配合useMemo,usecallback等api配合使用。
const DemoState = (props) => {
/* number为此时state读取值 ,setNumber为派发更新的函数 */
let [number, setNumber] = useState(0) /* 0为初始值 */
return (<div>
<span>{ number }</span>
<button onClick={ ()=> {
setNumber(number+1)
console.log(number) /* 这里的number是不能够即使改变的 */
} } ></button>
</div>)
}
state的值是不能即时改变的,只有当下一次上下文执行的时候,state值才随之改变。
useEffect 组件更新副作用钩子。
在function组件中,当组件完成挂载,dom渲染完成,做一些操纵dom,请求数据的时候, useEffect 可以优化性能。
如果我们需要在组件初次渲染的时候请求数据,那么useEffect可以充当class组件中的 componentDidMount。
如果不给useEffect执行加入限定条件,函数组件每一次更新都会触发effect ,那么也就说明每一次state更新,或是props的更新都会触发useEffect执行,此时的effect又充当了componentDidUpdate。
所以说合理的用于useEffect就要给effect加入限定执行的条件,也就是useEffect的第二个参数,这里说是限定条件,也可以说是上一次useeffect更新收集的某些记录数据变化的记忆,在新的一轮更新,useeffect会拿出之前的记忆值和当前值做对比,如果发生了变化就执行新的一轮useEffect的副作用函数,useEffect第二个参数是一个数组,用来收集多个限制条件 。
import React, {useEffect, useState} from 'react';
function ClassCmp(props) {
const [count, setCount] = useState(0)
// didMount, didUpdate
// didUpdate
useEffect(() => {
console.log(count)
document.title = `${count}`
/* 定时器 延时器等 */
const timer = setInterval(()=>console.log(666),1000)
/* 事件监听 */
window.addEventListener('resize', handleResize)
/* 此函数用于清除副作用 */
return function(){
clearInterval(timer)
window.removeEventListener('resize', handleResize)
}
}, [count])
return (
<div>
<h3>hooks</h3>
<h3>{count}</h3>
<button onClick={() => setCount(count + 1)}>add</button>
<h3>{useClock().toLocaleTimeString()}</h3>
</div>
);
}
export default ClassCmp;
如果我们需要在组件销毁的阶段,做一些取消dom监听,清除定时器等操作,那么我们可以在useEffect函数第一个参数,结尾返回一个函数,用于清除这些副作用。相当与componentWillUnmount。
useEffect是不能直接用 async await 语法糖的。用 async effect 可以对effect进行一层包装。
const asyncEffect = (callback, deps)=>{
useEffect(()=>{
callback()
},deps)
}
渲染更新之前的 useEffect。
useEffect 执行顺序 组件更新挂载完成 -> 浏览器dom 绘制完成 -> 执行useEffect回调 。
useLayoutEffect 执行顺序 组件更新挂载完成 -> 执行useLayoutEffect回调-> 浏览器dom 绘制完成。
useLayoutEffect 代码可能会阻塞浏览器的绘制 如果我们在useEffect 重新请求数据,渲染视图过程中,肯定会造成画面闪动的效果,而如果用useLayoutEffect ,回调函数的代码就会阻塞浏览器绘制,所以可定会引起画面卡顿等效果,那么具体要用 useLayoutEffect 还是 useEffect ,要看实际项目的情况,大部分的情况 useEffect 都可以满足的。
useRef: 获取元素 ,缓存数据。
和传统的class组件ref一样,react-hooks 也提供获取元素方法 useRef,它有一个参数可以作为缓存数据的初始值,返回值可以被dom元素ref标记,可以获取被标记的元素节点。
import {useRef} from "react";
const DemoUseRef = ()=>{
const dom= useRef(null)
const handerSubmit = ()=>{
/* 表单组件 dom 节点 */
console.log(dom.current)
}
return <div>
{/* ref 标记当前dom节点 */}
<div ref={dom} >表单组件</div>
<button onClick={()=>handerSubmit()} >提交</button>
</div>
}
export default DemoUseRef;import {useRef} from "react";
const DemoUseRef = ()=>{
const dom= useRef(null)
const handerSubmit = ()=>{
/* 表单组件 dom 节点 */
console.log(dom.current)
}
return <div>
{/* ref 标记当前dom节点 */}
<div ref={dom} >表单组件</div>
<button onClick={()=>handerSubmit()} >提交</button>
</div>
}
export default DemoUseRef;
useRef还有一个很重要的作用就是缓存数据。
缓存之前的值。
const Counter: React.FC = () => {
const [num, setNum] = useState(0)
const preNum = useRef()
const add = () => {
setNum(c => c + 1)
preNum.current = num
}
return (
<>
{num} - {preNum.current}
<button onClick={add}>change</button>
</>
)
}
export default Counter;
react-redux 在react-hooks发布后,用react-hooks重新了其中的Provide,connectAdvanced)核心模块,可以见得 react-hooks在限制数据更新,高阶组件上有这一定的优势,其源码大量运用useMemo来做数据判定。
/* 这里用到的useRef没有一个是绑定在dom元素上的,都是做数据缓存用的 */
/* react-redux 用userRef 来缓存 merge之后的 props */
const lastChildProps = useRef()
// lastWrapperProps 用 useRef 来存放组件真正的 props信息
const lastWrapperProps = useRef(wrapperProps)
//是否储存props是否处于正在更新状态
const renderIsScheduled = useRef(false)
react-redux中用useRef 对数据做的缓存。
//获取包装的props
function captureWrapperProps(
lastWrapperProps,
lastChildProps,
renderIsScheduled,
wrapperProps,
actualChildProps,
childPropsFromStoreUpdate,
notifyNestedSubs
) {
//我们要捕获包装props和子props,以便稍后进行比较
lastWrapperProps.current = wrapperProps //子props
lastChildProps.current = actualChildProps //经过 merge props 之后形成的 prop
renderIsScheduled.current = false
}
react-redux 用重新赋值的方法,改变缓存的数据源,避免不必要的数据更新, 如果选用useState储存数据,必然促使组件重新渲染 所以采用了useRef解决了这个问题。
useContext: 自由获取context, 获取父级组件传递过来的context值, 这个当前值就是最近的父级组件 Provider 设置的value值,useContext参数一般是由 createContext 方式引入 ,也可以父级上下文context传递 ( 参数为context )。useContext 可以代替 context.Consumer 来获取Provider中保存的value值。
export const MyContext = React.createContext()
function UseContextPage(props) {
const ctx = useContext(MyContext)
console.log(ctx)
return (
<div>
<h3>UseContextPage</h3>
<h3>{ctx.themeColor}</h3>
</div>
);
}
export default UseContextPage;
export const Context = React.createContext()
const DemoContext = ()=> {
const value = useContext(Context)
/* my name is alien */
return <div> my name is { value.name }</div>
}
/* 用Context.Consumer 方式 */
const DemoContext1 = ()=>{
return <Context.Consumer>
{/* my name is alien */}
{ (value)=> <div> my name is { value.name }</div> }
</Context.Consumer>
}
export default function UseContext(){
return <div>
<Context.Provider value={{ name:'alien' , age:18 }} >
<DemoContext />
<DemoContext1 />
</Context.Provider>
</div>
}
useReducer: 无状态组件中的redux。useReducer 是react-hooks提供的能够在无状态组件中运行的类似redux的功能api, 我们可以通过中间件的方式来增强dispatch redux-thunk redux-sage redux-action redux-promise都是比较不错的中间件。
useReducer 接受的第一个参数是一个函数,我们可以认为它就是一个reducer ,reducer的参数就是常规reducer里面的state和action,返回改变后的state, useReducer第二个参数为state的初始值 返回一个数组,数组的第一项就是更新之后state的值 ,第二个参数是派发更新的dispatch函数 。
dispatch 的触发会触发组件的更新,这里能够促使组件从新的渲染的一个是useState派发更新函数,另一个就 useReducer中的dispatch。
const DemoUseReducer = ()=>{
/* number为更新后的state值, dispatchNumbner 为当前的派发函数 */
const [ number , dispatchNumbner ] = useReducer((state,action)=>{
const { payload , name } = action
/* return的值为新的state */
switch(name){
case 'add':
return state + 1
case 'sub':
return state - 1
case 'reset':
return payload
}
return state
},0)
return <div>
当前值:{ number }
{ /* 派发更新 */ }
<button onClick={()=>dispatchNumbner({ name:'add' })} >增加</button>
<button onClick={()=>dispatchNumbner({ name:'sub' })} >减少</button>
<button onClick={()=>dispatchNumbner({ name:'reset' ,payload:666 })} >赋值</button>
{ /* 把dispatch 和 state 传递给子组件 */ }
{/*
useMemo: 能形成独立的渲染空间,能够使组件,变量按照约定好规则更新。memo的作用结合了pureComponent纯组件和 componentShouldUpdate功能。会对传进来的props进行一次对比,然后根据第二个函数返回值来进一步判断哪些props需要更新。
function UseMemoPage(props) {
const [count, setCount] = useState(0)
const [name, setName] = useState("")
// 依赖于count改变, 才触发此函数
const total = useMemo(() => {
console.log("@")
let res = 0;
for (let i = 0; i < count; i++){
res += i
}
return res
}, [count])
return (
<div>
<h3>UseMemoPage</h3>
<h3>{total} - {count}- {name}</h3>
<button onClick={() => {setCount(count + 1)}}>add</button>
<input value={name} onChange={event => {setName(event.target.value)}}/>
</div>
);
}
export default UseMemoPage;
如果我们应用useMemo根据依赖项合理的颗粒化我们的组件,能起到很棒的优化组件的作用。
useMemo和useCallback接收的参数都是一样,都是在其依赖项发生变化后才执行,都是返回缓存的值,区别在于useMemo返回的是函数运行的结果,useCallback返回的是函数。
这个回调函数是经过处理后的也就是说父组件传递一个函数给子组件的时候,由于是无状态组件每一次都会重新生成新的props函数,这样就使得每一次传递给子组件的函数都发生了变化,这时候就会触发子组件的更新,这些更新是没有必要的,此时我们就可以通过usecallback来处理此函数,然后作为props传递给子组件。
/* 用react.memo */
const DemoChildren = React.memo((props)=>{
/* 只有初始化的时候打印了 子组件更新 */
console.log('子组件更新')
useEffect(()=>{
props.getInfo('子组件')
},[])
return <div>子组件</div>
})
const DemoUseCallback=({ id })=>{
const [number, setNumber] = useState(1)
/* 此时usecallback的第一参数 (sonName)=>{ console.log(sonName) }
经过处理赋值给 getInfo */
const getInfo = useCallback((sonName)=>{
console.log(sonName)
},[id])
return <div>
{/* 点击按钮触发父组件更新 ,但是子组件没有更新 */}
<button onClick={ ()=>setNumber(number+1) } >增加</button>
<DemoChildren getInfo={getInfo} />
</div>
}
useCallback ,必须配合 react.memo pureComponent ,否则不但不会提升性能,还有可能降低性能。
react-hooks的诞生,也不是说它能够完全代替class声明的组件,对于业务比较复杂的组件,class组件还是首选,只不过我们可以把class组件内部拆解成funciton组件,根据业务需求,哪些负责逻辑交互,哪些需要动态渲染,然后配合usememo等api,让性能提升起来。react-hooks使用也有一些限制条件,比如说不能放在流程控制语句中,执行上下文也有一定的要求。