React Hook

react 16.8 以后加上了 react hook,它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。16.8之前,react组件可以分为类组件和函数组件。

  • 函数组件一定是无状态组件,展示型组件(渲染组件)一般是无状态组件;
  • 类组件既可以是有状态组件,又可以是无状态组件。类组件也可以叫容器组件,一般有交互逻辑和业务逻辑,而容器型组件一般是有状态组件。

我们为什么要拥抱react hook?由于类组件存在以下几点问题:

  • 组件变得复杂和难以维护,业务变得复杂之后,组件之间共享状态变得频繁,此时组件将变得非常难以理解和维护,复用状态逻辑更是难上加难。
  • 满天class导致的热重载和性能问题,class自生具有的复杂度和组件嵌套过深props层级传递。
  • 函数式组件没有状态

下面逐一介绍官方提供的hook API。
1.useState()
作用:返回一个状态以及能修改这个状态的setter,在其他语言称为元组(tuple),一旦mount之后只能通过这个setter修改这个状态。


useState函数申明

2.useEffect(callback, arr)
作用:处理函数组件中的副作用,如异步操作、延迟操作等。useEffect有两个参数,callback和数组依赖项,无arr时相当于componentDidMount生命周期,有arr时相当componentDidMount和componentDidUpdata生命周期。如果callback中有return,则相当于componentWillUnmount。

3.useContext
作用:跨组件共享数据钩子,使用可分为三步:

  • 首先使用React.createContext API创建Context,由于支持在组件外部调用,因此可以实现状态共享
export const MyContext = React.createContext(null);
  • 使用Context.Provider API在上层组件挂载状态
<MyContext.Provider value={value}>
  <childComponent />
</MyContext.Provider>
  • 获取上层组件中距离当前组件最近的<MyContext.Provider> 的 value
const value = useContext(MyContext)   // MyContext 为 context 对象(React.createContext 的返回值) 

useContext和传统的props传参分别适用于那些场景
useContext的应用场景:
1.全局状态的定义,即可以被不同层级的组件所需要。
2.多个组件之间传参(他们之间可能是跨多层级即祖孙关系传参)时。
传统props的应用场景:
如果普通的父子组件之间传参,即父子组只有单纯的一层时,用props传参更省事

4.useReducer

语法:const [state, dispatch] = useReducer(reducer, initialArg, init);

作用:用于管理复杂的数据结构(useState一般用于管理扁平结构的状态),基本实现了redux的核心功能。useState的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。

const initialState = {count: 0};
const reducer = (state, action)=> {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      return state;
  }
}
const [state, dispatch] = useReducer(reducer, initialState);
return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );

5.useMemo、useCallback
这俩个Api与性能优化有关。react中,性能的优化点在于:

  • 调用setState,就会触发组件的重新渲染,无论前后的state是否不同
  • 父组件更新,子组件也会自动的更新

基于上面的两点,我们通常的解决方案是:使用immutable进行比较,在不相等的时候调用setState;在shouldComponentUpdate中判断前后的props和state,如果没有变化,则返回false来阻止更新。
在hooks出来之后,我们能够使用function的形式来创建包含内部state的组件。但是,使用function的形式,失去了上面的shouldComponentUpdate,我们无法通过判断前后状态来决定是否更新。而且,在函数组件中,react不再区分mount和update两个状态,这意味着函数组件的每一次调用都会执行其内部的所有逻辑,那么会带来较大的性能损耗。因此useMemo 和useCallback就是解决性能问题的杀手锏。

useCallback和useMemo的参数跟useEffect一致。useMemo和useCallback都会在组件第一次渲染的时候执行,之后会在其依赖的变量发生改变时再次执行;并且这两个hooks都返回缓存的值,useMemo返回缓存的变量,useCallback返回缓存的函数。

语法:
useMemo:const A = useCallback(fnB, [a]) 调用fnB函数并返回其结果
useCallback:const fnA = useCallback(fnB, [a]) 返回fnB函数

通过一个例子来看useMemo的作用:

const [count, setCount] = useState(1);
const [val, setValue] = useState('');
const expensive = () => {
        console.log('compute');
        let sum = 0;
        for (let i = 0; i < count * 100; i++) {
            sum += i;
        }
        return sum;
}
return <>
        <h4>{count}-{val}-{expensive()}</h4>
        <div>
            <button onClick={() => setCount(count + 1)}>+c1</button>
            <input value={val} onChange={event => setValue(event.target.value)}/>
        </div>
</>;

不使用useMemo,无论count还是val改变都会执行expensive()

const [count, setCount] = useState(1);
const [val, setValue] = useState('');
const expensive = useMemo(() => {
        console.log('compute');
        let sum = 0;
        for (let i = 0; i < count * 100; i++) {
            sum += i;
        }
        return sum;
}, [count])
return <>
        <h4>{count}-{val}-{expensive()}</h4>
        <div>
            <button onClick={() => setCount(count + 1)}>+c1</button>
            <input value={val} onChange={event => setValue(event.target.value)}/>
        </div>
</>;

使用useMemo,只有在count发生改变使才会会执行expensive()

useCallback跟useMemo比较类似,但是使用场景不同:比如有一个父组件,其中包含子组件,子组件接收一个函数作为props;通常而言,如果父组件更新了,子组件也会执行更新;但是大多数场景下,更新是没有必要的,我们可以借助useCallback来返回函数,然后把这个函数作为props传递给子组件;这样,子组件就能避免不必要的更新。

const [count, setCount] = useState(1);
const [val, setVal] = useState('');

const callback = useCallback(() => {
    console.log('callback执行');
    return count;
}, [count]);

return <>
    <h4>{count}</h4>
    <Child callback={callback} />
    <div>
        <button onClick={() => setCount(count + 1)}>+</button>
        <input value={val} onChange={event => setVal(event.target.value)} />
    </div>
</>

const Child = (props: any) => {
    const [count, setCount] = useState(() => props.callback());

    useEffect(() => {
        setCount(props.callback());
    }, [props.callback]);

    return <div>{count}</div>;
};

6.useRef

语法:const refContainer = useRef(initialValue);

useRef是一个方法,返回一个可变的 ref 对象;其 .current 属性被初始化为传入的参数(initialValue);可以保存任何类型的值:dom、对象等任何可变值;返回的 ref 对象在组件的整个生命周期内保持不变;修改 ref 的值是不会引发组件的重新 render 。

useRef非常常用的一个操作,访问DOM节点,对DOM进行操作,监听事件等等,如下:

const inputEl = useRef(null);
const onButtonClick = () => {
    // `current` 指向已挂载到 DOM 上的文本输入元素
    inputEl.current.focus();
};
return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
);

除了传统的用法之外,它还可以“跨渲染周期”保存数据。

const likeRef = useRef(1);
const [like, setLike] = useState(1);
const onButtonClick = () => {
    setTimeout(() => {
        console.log(likeRef.current); //11
        console.log(like); // 1
    }, 2000);
};
return (
  <>
    <button
        onClick={() => {
           likeRef.current++;
           setLike(like + 1);
    }}>
    {like}
    </button>
    <button onClick={onButtonClick}>打印like</button>
  </>
)

在上面的例子中,点击了打印like按钮后,连续点击数字按钮,会发现2s后likeRef.current打印出11,而like打印出1。

因为,在任意一次渲染中,props和 state 是始终保持不变的,如果props和state在任意不同渲染中是相互独立的话,那么使用到他们的任何值也是独立的。所以onButtonClick时拿到的时未点击数字按钮时的like值。
而ref 在所有 render 都保持着唯一的引用,因此所有的对 ref 的赋值或者取值拿到的都是一个最终的状态,而不会存在隔离。

7.useImperativeHandle

语法:useImperativeHandle(ref, createHandle, [deps])

当userRef用于一个组件时,useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值;如果不使用,父组件的ref(chidlRef)访问不到任何值(childRef.current==null);且useImperativeHandle 应当与 forwardRef 一起使用。

const RefDemo= ()=>{
    const childRef = useRef<any>(null);
    const onButtonClick = () => {
        childRef.current?.say();
    };
    return (
    <div>
        <Child ref={childRef} />
        <button onClick={onButtonClick}>say123</button>
    </div>
    )
};
export default RefDemo;
const Child = React.forwardRef((props, ref) => {
    useImperativeHandle(ref, () => ({
        say: () => {
            console.log('123');
        },
    }));
    return (
        <>
            <h4>子组件</h4>
        </>
    );
});
  • React.forwardRef会创建一个React组件,这个组件能够将其接受的ref属性转发到其组件树下的另一个组件中。
  • React.forwardRef接受渲染函数作为参数,React将使用prop和ref作为参数来调用此函数。

8.useLayoutEffect
用法与useEffect 相同,但它会在所有的 DOM 变更之后同步调用(立即执行)。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。由于会在浏览器进行任何绘制之前运行完成,阻塞了浏览器的绘制。
使用场景:当useEffect里面的操作需要处理DOM,并且会改变页面的样式,就需要用这个,否则可能会出现闪屏问题。

9.useDebugValue
useDebugValue 用于在 React 开发者工具中显示 自定义 Hook 的标签。
useDebugValue 接受一个格式化函数作为可选的第二个参数。该函数只有在 Hook 被检查时才会被调用。它接受 debug 值作为参数,并且会返回一个格式化的显示值。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • React Hook是React函数式组件,它不仅仅有函数组件的特性,还带有React框架的特性。所以,官网文档多...
    娜姐聊前端阅读 439评论 3 1
  • 一、组件类 React的核心是组件, 在v16.8之前,组件的标准写法是类(class)。 以下为一个简单的组件类...
    郭_小青阅读 687评论 1 5
  • Hook 是 react 16.8 推出的新特性,具有如下优点:Hook 使你在无需修改组件结构的情况下复用状态逻...
    林木木road阅读 780评论 0 1
  • github的MD[https://github.com/liusanhong/study/blob/master...
    半个木头人阅读 265评论 0 0
  • 前言 自react16.8发布了正式版hook用法以来,我们公司组件的写法逐渐由class向函数式组件+hook的...
    大春春阅读 3,933评论 3 7