前言
最近的换写React项目了,好久没有写React,还动不动就想class🤣,现在跟上时代,重新学习React Hook.Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
为什么有Hook?
1、在组件之间复用状态逻辑比较难,需要用到高阶组件或者render props,Hook可以无需修改组件结构的情况下,复用状态逻辑。
2、Hook可以将组件中相互关联的部分拆分成更小的函数,方便理解;
3、不再使用class和this,更方便上手和理解;
使用Hook的注意事项
1、只能在最外层函数中调用Hook。(不要在循环、条件判断、子函数中调用)
React在创建hook时,以链表的解构存储,前一个hook中next指针指向下一个hook。在组件更新时会进行hook的一一对应的比对,所以要保证每次hook数量一致,位置相同。循环、条件判断中使用hook,会导致数量和位置的变化,产生错误。
2、只在react的函数组件中调用。
常用的HooK
1.useState
- 作用:在组件内部添加一个state,React会在重复渲染时保留这个state。
- 参数: 1个,state的初始值initialState;
const [state,setState] =useState(initialState)
- 返回值 :2个
1)state 当前状态,在初始渲染时,state就是initialState
2)setState更新当前state的函数,他接受一个新的state,并且将该组件的一次渲染加入队列,他有点类似类组件中this.setState,但并不会将新旧state合并
//在函数组件中
const [state,setState] =useState({name:"lily",age:18})
return(
<button onclick={setState({age:20})}>改变年龄<button>
//这里使用useState不会将新旧state,现在state为{age:20},name属性就没有了
//所以需要注意 onclick={setState({...state,age:20})}
)
- 特点
1) 每次渲染都是独立的闭包,拥有当前自己的props\state\函数
2)函数式更新
//如果新的状态需要依赖旧的状态,可以使用函数式更新
setState(state =>({...state,age:state.age +1}))
//将旧状态传入,返回新的状态
3)懒加载初始化state
//如果state中的值需要复杂计算
const [state,setState] =useState(()=> ({name:"lily",age:12}))
- 性能优化(减少渲染次数)
1)Object.is
在调用state的更新函数时,React会先进行Object.is的比较算法
如果state不相同,进行子组件渲染和effect,否则不进行刷新渲染
2)useMemo(缓存值)
某些情况下组件中的值需要依赖state进行计算后得到,由于每次组件更新都会去重新计算,为了减少性能消耗,可以使用useMemo缓存值,仅仅当依赖改变时,才重新计算。
- 参数:计算函数,依赖数组
则函数仅会在某个依赖项改变时才重新计算memoized 值。
const [count, setCount] = useState(0);
//expensive 需要依赖count计算得到
const expensive = useMemo(() => {
let sum = 0;
for (let i = 0; i < count; i++) {
sum += i;
}
return sum;
//只有count变化,这⾥才重新执⾏
}, [count]);
3)useCallback (缓存回调函数)
由于每次组件更新都会内部函数都会重新创建一次,为了节省性能,可以使用useCallback,使内联回调函数仅仅在传入依赖项改变时进行更新。
- 参数:内联回调函数,依赖数组
则函数仅会在某个依赖项改变时才进行更新。
const [count, setCount] = useState(0);
const addClick = useCallback(() => {
setCount(count+1)
}, [count]);
4)memo (缓存组件)
父组件更新子组件也会重新创建,为节省性能,可用memo将子组件缓存,仅仅当props改变时,组件才进行更新
const Child = (props)=>{
return <div>{props.age}</div>
}
//这里只会在props改变的时候重新渲染
Child =memo(Child)
const Father=()=>{
const [name,setName]=useState("lily")
const [age,setAge]=useState(18)
return(
<>
<button onclick={setAge(age+1)}>改变年龄<button>
<input onChange={e=>(setName(e.target.value))}>改变姓名<button>
<Child age={age}/>
</>
)
}
5)useRef (缓存ref)
同理,用useRef可对React.creactRef对象进缓存
2. useReducer
- 作用:编写自定义hook或者在某些改变state逻辑复杂的情况下替代useState,用法类似redux,useState是useReducer的语法糖。
- 参数: 3个,reducer方法 、initialState初始值,init;
const [state,dispatch] =useReducer(reducer ,initialState)
-
返回值 :2个
1)state当前状态,在初始渲染时,state就是initialState
2)dispatch派发方法,接受参数action,改变当前state,并且将该组件的一次渲染加人队列。 -
自定义hook
//实现一个useState
const useState=(initalState)=>{
const reducer = useCallback(_state,action)=>{
return action.paylaod
}
const [state,dispatch] = useReducer(reducer, initalState)
const setState = (state)=>{
dispatch({paylaod:state})
}
return [state,setState]
}
3.useContext
- 作用:提供更方便的方法从React上下文中拿到consumer的值
参数: 1个,context(React.createContext);
const value=useContext(context)
- 返回值 :1个 consumer的值
4.useEffect
- 作用:在React渲染阶段,改变DOM、添加订阅、设置定时器等操作会破坏UI一致性,含有副作用的操作将不被允许,使用useEffect完成副作用的操作,传入函数会在组件渲染后执行.
- 参数: 2个,didUpdate包含副作用的回调函数,在组件渲染后执行,[state]依赖项,传入依赖项,仅在依赖项改变后的执行
useEffect(didUpdate,[state])
- 返回值 :1个 清除副作用函数,在组件销毁前执行,相当于componentWillUnmount,可进行定时器,事件监听的移除操作
- 特点
由于useEffect的副作用函数在组件渲染后执行,他和class组件中的 生命周期componentDidMount/componentDidUpdate和componentWillUnmount有相同的用途
5.useRef、useImperativeHandle
- useRef作用:对ref对象进缓存,ref 对象在组件的整个生命周期内保持不变。
- 参数: 1个,initialValue,ref.curren初始化的值
function FocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` 指向已挂载到 DOM 上的文本输入元素
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
- useImperativeHandle作用:有些情况下我们需要父组件把ref传递给子组件,仅仅使用forwardRef将子组件的ref属性都暴露给父组件是危险的,也违反了封装原则,useImperativeHandle允许在子组件中把自定义实例附加到父组件传过来的ref上,减少暴露给父组件的属性
- 参数: 3个,ref, createHandle处理函数,其返回值会替换父组件传来的ref.current, [state]依赖项
useImperativeHandle(ref, createHandle, [state])
- 例子
let Child = (props, ref) =>{
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus()
}
}));
return <input ref={inputRef} />;
}
Child = forwardRef(Child );
const Father=() => {
const ref = useRef(null);
return (
<>
<Child ref={ref}/>
<button onClick={()=>{ ref.current.focus()}}>聚焦</button >
</>
)
}
6.useLayoutEffect
- 作用 :与useEffect相同,都是用来执行副作用。
- 特点 :useEffect是在渲染之后调用effect,也就是下一桢执行,是异步的;
而useLayoutEffect在当前帧Render tree计算布局(Layout)信息后,渲染前,同步调用effect。官方文档建议使用标准useEffec。
7.自定义Hook
- 定义:函数以use开头并且在函数内部调用其他hooks,并且返回一个数组,可以实现逻辑复用。
- 例子 :
//实现一个thunk
const useThunk =(reducer,initalState)=>{
const [state,dispatch] =useReducer(reducer,initalState)
const thunkDispatch = (action)=>{
if( action instanceof Function){ //如果action是函数,传入新的dispatch执行action
return action (thunkDispatch,()=>state)
}else{
dispatch(action )
}
return [state,thunkDispatch]
}
结尾
现在就只了解到这么多。还有什么没写到的日后更新。
网友评论