react hooks作用
之前的react组件主要有两种方式实现:Class方式和function方式。一般无状态组件可以使用function方式写,大部分组件还是使用Class方式写的。
两种方式存在的问题:
- Class组件:1.组件复杂时,很难拆分;2.组件拆分颗粒度较细时,基础组件上很难加东西;3.多个组件间的重复代码很难复用。
- function组件:只能渲染无状态组件。 那么使用Hooks解决了啥问题呢?
- 可以使用function方式+hooks来编写有状态的组件;
- 相同逻辑的代码可以剥离出来,解决了代码复用问题;
- 组件拆分更容易
常用的react hooks
useState
useState是用来声明状态变量,例子如下:
import { useState } from 'react';
const [count, setCount] = useState(0);
useState这个函数接受的参数是状态的初始值,返回一个数组,数组的第0位是当前的状态值,第1位是可以改变状态值的方法函数。上面例子中使用ES6的解构方式获取数据。
初始状态可以是数字、字符串、对象、数组,甚至是方法(如果是方法的话,执行该方法,返回值作为初始状态)。
注意:React Hooks不能在条件判断语句、循环函数中使用,因为它必须有完全一样的渲染顺序。
useEffect
useEffect支持副作用(如异步请求,手动操作Dom,定时任务等),用来代替Class组件中的生命周期函数——componentDidMount(首次渲染)和componentDidUpdate(更新state导致的渲染)。
import { useEffect } from 'react';
useEffect(()=>{
console.log('useEffect')
}, []);
useEffect的第二个参数是可选的,用来优化useEffect,只有参数发生变化时,才能执行副作用函数。 解绑副作用是如何实现的呢?
useEffect(()=>{
console.log('useEffect')
return ()=>{
console.log('useEffect off')
}
},[])
上面的例子就实现了componentDidMount+componentwillUnMount的生命周期,重点是第二个参数传了空数组。
useContext
实现跨层级传值,让父子组件传值更简单。
父组件:
import { createContext } from 'react';
const CountContext = createContext();
function Example(){
const [ count , setCount ] = useState(0);
return (
<div>
<CountContext.Provider value={count}>
<Counter/>
</CountContext.Provider>
</div>
)
}
export default Example;
子组件:
import React, { useContext } from 'react';
function Counter(){
const count = useContext(CountContext) //一句话就可以得到count
return (<h2>{count}</h2>)
}
useReducer
和redux很相近,在state复杂的时候使用,使代码具有更好的可读性和可维护性。
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
注意:使用 useState 获取的 setState 方法更新数据时是异步的;而使用 useReducer 获取的 dispatch 方法更新数据是同步的。
useCallback和useMemo
主要用于React hooks的性能优化。 例子如下:
import React , { useMemo, useCallback} from 'react';
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
const memoizedCallback = useCallback(() => { doSomething(a, b); }, [a, b]);
useCallback接受函数和一个数组输入,并返回的一个缓存版本的回调函数,仅当重新渲染时数组中的值发生改变时,才会返回新的函数实例。
useMemo与useCallback类似, 返回的是一个缓存的值。仅当重新渲染时数组中的值发生改变时,回调函数才会重新计算缓存数据,这可以避免在每次重新渲染时都进行复杂的数据计算。
唯一的区别是:useCallback 不会执行第一个参数函数,而是将它返回给你,而 useMemo 会执行第一个函数并且将函数执行结果返回给你。
useRef 和 useImperativeHandle
useRef用来获取DOM元素,保存变量。 例子如下:
const refContainer = useRef(initialValue);
useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。
useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值。 useImperativeHandle 应当与 forwardRef 一起使用:
function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);
useLayoutEffect
useLayoutEffect与useEffect具有相同的api,都是执行副作用操作。
useLayoutEffect的用途:当你的useEffect里面有操作DOM元素,并改变页面样式时,就需要用到useLayoutEffect,否则就可能会出现闪屏的问题。
产生闪屏原因是:useEffect是异步的,其异步就是利用requestIdleCallback,在浏览器空闲时间执行传入的callback;而useLayoutEffect是同步的,useLayoutEffect里面的callback函数会在DOM更新完以后立即执行,但是会在浏览器进行任何绘制之前运行完成,阻塞了浏览器的绘制。大部分情况下,用哪个都一样,但是如果副作用执行时间过长,如存在大量计算,useLayoutEffect就会造成渲染阻塞,所以官方推荐尽量使用useEffect。