hooks = 函数组件 + 状态。hooks是对函数式组件的极大加强。
react-hooks是react16.8以后,react新增的钩子API,目的是增加代码的可复用性,逻辑性,弥补函数组件没有生命周期,没有数据管理状态state的缺陷。
useState出现,使得react无状态组件能够像有状态组件一样,可以拥有自己state。
useState的参数可以是一个具体的值,也可以是一个函数,函数返回作为初始值。
usestate钩子返回的是一个数组,数组第一项用于读取此时的state值,第二项为派发数据更新,组件渲染的函数,该函数的参数即是需要更新的值,在调用更新函数之后,state的值是不能立即改变的,只有当下一次上下文执行的时候,state值才随之改变。。
useState和useReduce作为能够触发组件重新渲染的hooks,我们在使用useState的时候要特别注意的是,useState派发更新函数的执行,就会让整个function组件从头到尾执行一次,所以需要配合useMemo,usecallback等api配合使用,这就是我说的为什么滥用hooks会带来负作用的原因之一了。
const a =1
const DemoState = (props) => {
const [num, setNum] = useState(1)
/* useState 参数如果是函数 则处理复杂的逻辑 ,返回值为初始值 */
const [number, setNumber] = useState(()=>{
return a===1 ? 1 : 2
}) /* 1为初始值 */
return (<div>
<span>{ num }</span>
<span>{ number }</span>
<button onClick={ ()=>setNumber(number+1) } ></button>
</div>)
}
useEffect的第二个参数,定义了执行useEffect的限定条件,也可以说是上一次useeffect更新收集的某些记录数据变化的记忆,在新的一轮更新,useeffect会拿出之前的记忆值和当前值做对比,如果发生了变化就执行新的一轮useEffect,useEffect第二个参数是一个数组,用来收集多个限制条件。
// 只有在组件初次渲染时执行一次
useEffect(() => {
// 执行数据
},[])
// 相当于componentDidMount
const Demo = ({ a }) => {
// 当props的a发生变化时执行
useEffect(()=>{
// 执行数据
},[ a ])
return (<div >hello</div>)
}
如果不给useEffect执行加入限定条件,函数组件每一次更新都会触发effect ,那么也就说明每一次state更新,或是props的更新都会触发useEffect执行。
useEffect(() => {
// 执行数据
})
// 相当于componentDidUpdate
如果我们需要在组件销毁的阶段,做一些比如取消dom监听,清除定时器等操作,那么我们可以在useEffect函数第一个参数,结尾返回一个函数,用于清除这些副作用。相当与componentWillUnmount。
const Demo = ({ a }) => {
useEffect(()=>{
/* 定时器 延时器等 */
const timer = setInterval(()=>console.log(666),1000)
/* 此函数用于清除副作用 */
return function(){
clearInterval(timer)
}
},[ a ])
return (<div >hello</div>)
}
useEffect 的回调参数返回的是一个清除副作用的 clean-up 函数。因此无法返回 Promise,更无法使用 async/await
/* 错误用法 ,effect不支持直接 async await 装饰的 */
useEffect(async ()=>{
/* 请求数据 */
const res = await getUserInfo(payload)
},[])
// 解决:
// 方法一:
const MyFunctionnalComponent: React.FC = props => {
useEffect(() => {
async function anyNameFunction() {
await loadContent();
}
anyNameFunction();
}, []);
return <div></div>;
};
// 方法二:IIFE
const MyFunctionnalComponent: React.FC = props => {
useEffect(() => {
(async function anyNameFunction() {
await loadContent();
})();
}, []);
return <div></div>;
};
和传统的class组件ref一样,react-hooks 也提供获取元素方法 useRef,它有一个参数可以作为缓存数据的初始值,返回值可以被dom元素ref标记,可以获取被标记的元素节点.
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>
)
}
缓存数据
当然useRef还有一个很重要的作用就是缓存数据,我们知道usestate ,useReducer 是可以保存当前的数据源的,但是如果它们更新数据源的函数执行必定会带来整个组件从新执行到渲染,如果在函数组件内部声明变量,则下一次更新也会重置,如果我们想要悄悄的保存数据,而又不想触发函数的更新,那么useRef是一个很棒的选择。
const currenRef = useRef(InitialData)
// 获取
currenRef.current
// 改变
currenRef.current = newValue
useRef第一个参数可以用来初始化保存数据,这些数据可以在current属性上获取到 ,也可以通过对current赋值新的数据源。
我们可以使用useContext ,来获取父级组件传递过来的context值,这个当前值就是最近的父级组件 Provider 设置的value值,useContext参数一般是由 createContext 方式引入 ,也可以父级上下文context传递 ( 参数为context )。useContext 可以代替 Context.Consumer 来获取Provider中保存的value值
/* 用useContext方式 */
const DemoContext = ()=> {
const value:any = 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 ()=>{
return
(<div>
<Context.Provider value={{ name:'alien' , age:18 }} >
<DemoContext />
<DemoContext1 />
</Context.Provider>
</div>)
}
useReducer 是react-hooks提供的能够在无状态组件中运行的类似redux的功能api。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>
</div>
}
useMemo我认为是React设计最为精妙的hooks之一,优点就是能形成独立的渲染空间,能够使组件,变量按照约定好规则更新。渲染条件依赖于第二个参数deps。 我们知道无状态组件的更新是从头到尾的更新,如果你想要从新渲染一部分视图,而不是整个组件,那么用useMemo是最佳方案,避免了不需要的更新,和不必要的上下文的执行,在介绍useMemo之前,class组件可以用componentShouldUpdate来限制更新次数,那么memo就是函数组件的ShouldUpdate ,memo的作用结合了pureComponent纯组件和 componentShouldUpdate功能,会对传进来的props进行一次对比,然后根据第二个函数返回值来进一步判断哪些props需要更新。
/* memo包裹的组件,就给该组件加了限制更新的条件,是否更新取决于memo第二个参数返回的boolean值, */
function Child({seconds}){
console.log('I am rendering');
return (
<div>I am update every {seconds} seconds</div>
)
};
function areEqual(prevProps, nextProps) {
if(prevProps.seconds===nextProps.seconds){
return true
}else {
return false
}
}
export default React.memo(Child,areEqual)
useMemo的应用理念和memo差不多,都是判定是否满足当前的限定条件来决定是否执行useMemo的callback函数,而useMemo的第二个参数是一个deps数组,数组里的参数变化决定了useMemo是否更新回调函数,useMemo返回值就是经过判定更新的结果。它可以应用在元素上,应用在组件上,也可以应用在上下文当中。如果又一个循环的list元素,那么useMemo会是一个不二选择,接下来我们一起探寻一下useMemo的优点
/* 用 useMemo包裹的list可以限定当且仅当list改变的时候才更新此list,这样就可以避免selectList重新循环 */
{useMemo(() => (
<div>{
selectList.map((i, v) => (
<span
className={style.listSpan}
key={v} >
{i.patentName}
</span>
))}
</div>
), [selectList])}