react hooks 初步使用(一)

一、前言

那一天我二十一岁,在我一生的黄金时代,我有好多奢望。我想爱,想吃,还想在一瞬间变成天上半明半暗的云,我觉得自己会永远生猛下去,什么也锤不了我。

<p align="right">《黄金时代》王小波</p>

二、关于Hooks

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。Hook 的本质还是 JavaScript 函数,但是在使用它时需要遵循两条规则

只在最顶层使用 Hook

不要在循环,条件或嵌套函数中调用 Hook,确保总是在你的 React 函数的最顶层以及任何 return 之前调用他们。

只在 React 函数中调用 Hook

不要在普通的 JavaScript 函数中调用 Hook,你可以:

  1. 在 React 的函数组件中调用 Hook
  2. 在自定义 Hook 中调用其他 Hook

三、Hooks API

hooks的api有如下几个,按照官方文档划分了一下。

react+hooks.png
useState

这个算是最简单的 hook 函数了,它的作用就是初始化一个值,返回一个 和 initialState 相同的 state,以及更新 state 的函数 setState

const [state, setState] = useState(initialState);

我们来使用一下 useState

import React, { useState } from 'react';

const Index = () => {
  // setNumber 更新number函数
  const [number, setNumber] = useState(0);

  return (
    <>
      <div>{number}</div>
      <button onClick={() => setNumber(number + 1)}>Add Number</button>
      <button onClick={() => setNumber(number - 1)}>Sub Number</button>
    </>
  );
};

export default Index;

我们可以看到 useState 初始化一个值 number 为 0 之后返回一个数组,里面包含 number 本身 和 改变 number 值的函数 setNumber 我们通过两个按钮来绑定 setNumber 来改变 number。实现 number 值的加一减一。

useEffect

useEffect 就是用来在函数组件中模拟类组件中的生命周期函数的。接受两个参数,第一个参数就是一个箭头函数。箭头函数返回一个函数,这个函数就是组件卸载的时候触发。(这里举个🌰:我们要监听页面的 message 的时候,那么就在 render dom 的后面监听,在 dom remove 的时候移除监听就行了)

关于第二个参数[deps]

  1. 没有第二个参数的时候,当 number 发生变化的时候,组件会 remove 后 render,有点像 shouldComponentUpdate生命周期。
  2. 当第二个参数为 [] 的时候,只会执行初始化的 render, 这个时候相当于生命周期 componentDidMount
  3. 当第二个参数不为空的时候,只有存在数组中的参数发生改变的时候,就会 remove 后 render,就是更新组件。
import React, { useEffect, useState } from 'react';

const Index = () => {
  // setNumber 更新number函数
  const [number, setNumber] = useState(0);
  useEffect(() => {
    console.log('render dom');
    return () => {
      console.log('dom remove');
    };
  }, [number]);

  return (
    <>
      <div>{number}</div>
      <button onClick={() => setNumber(number + 1)}>Add Number</button>
      <button onClick={() => setNumber(number - 1)}>Sub Number</button>
    </>
  );
};

export default Index;
useContext

useContext 接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定。其实这里的 useContext 就和 context.Consumer 的效果差不多了。这里提一下 context.Consumer 的用法

<MyContext.Consumer>
  {value =>  {/* 基于 context 值进行渲染 */}}
</MyContext.Consumer>

看个🌰,定义了一个主题对象,里面有两个主题 light 和 dark,我们用 ThemeContext 接收一个context 对象, 使用 const theme = useContext(ThemeContext); 去订阅它,而返回的 theme 值是距离当前组件最近的 <ThemeContext.Provider value={themes.dark}> 的value prop。所以下面渲染的theme就是 dark主题。

import React, { useContext } from 'react';

const themes = {
  light: {
    foreground: '#000000',
    background: '#eeeeee',
  },
  dark: {
    foreground: '#ffffff',
    background: '#222222',
  },
};

const ThemeContext = React.createContext();

function Index() {
  return (
    // 这里传入light主题
    <ThemeContext.Provider value={themes.dark}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Toolbar(props) {
  return <ThemedButton />;
}

function ThemedButton() {
  const theme = useContext(ThemeContext);
  return (
    <button style={{ background: theme.background, color: theme.foreground }}>
      I am styled by theme context!
    </button>
  );
}

export default Index;
useReducer

useReducer useState 的替代方案,它接收一个形如 (state, action) => newState 的 reducer,(和 redux 一样)在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值以下是用 reducer 重写 useState 一节的计数器示例。

import React, { useReducer } from 'react';

function reducer(state, action) {
  console.log(state, action);
  switch (action.type) {
    case 'add':
      return { count: state.count + 1 };
    case 'sub':
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
}

function Index() {
  //   return <h1>1</h1>;
  const [state, dispatch] = useReducer(reducer, { count: 0 });

  return (
    <>
      <h1>count:{state.count}</h1>
      <button onClick={() => dispatch({ type: 'add' })}>+</button>
      <button onClick={() => dispatch({ type: 'sub' })}>-</button>
    </>
  );
}

export default Index;
useCallback

useCallback(fn, deps) 相当于 useMemo(() => fn, deps)。可以看出它是用来缓存函数的,下面是我mentor在reviewcode的时候提出的优化建议 (标注cr的)

const TreeDemo = () => {
  // cr: 函数作为子组件的props时,得考虑性能,用useCallback;
  //   const onSelect = (selectedKeys) => {
  //     console.log('selected', selectedKeys);
  //   };

  //   const onCheck = (checkedKeys) => {
  //     console.log('onCheck', checkedKeys);
  //   };

  //reviewcode
  const onSelect = useCallback(selectedKeys => {
    console.log('selected', selectedKeys);
  });

  const onCheck = useCallback(checkedKeys => {
    console.log('onCheck', checkedKeys);
  });

  // cr: 默认值用状态去控制,如果有外部传入的值,优先使用外部的值
  return (
    <Tree
      checkable
      //默认展开指定的树节点
      defaultExpandedKeys={['0-0', '0-0-0', '0-0-1']}
      //默认选中的树节点
      defaultSelectedKeys={['0-0']}
      //默认选中复选框的树节点
      defaultCheckedKeys={['0-0-1', '0-0-0-1']}
      onSelect={onSelect}
      onCheck={onCheck}
      treeData={treeData}
    />
  );
};
useMemo

useMemo 用来缓存数据,实例如下 sum是个需要计算的参数,当我们没使用memo做缓存的时候,在更新rank的时候也会重新计算 sum的值,这显然是不合理的,我们只需要在更新count的时候才会去重新计算sum的值。这种优化有助于避免在每次渲染时都进行高开销的计算。

import React, { useMemo, useState } from 'react';

function Index() {
  const [count, setCount] = useState(100);
  const [rank, setRank] = useState(1);

  let sum = useMemo(() => {
    console.log('calculate');
    let sum = 0;
    for (let i = 0; i <= count; i++) {
      sum += i;
    }
    return sum;
  }, [count]);

  return (
    <>
      <h1>{rank}</h1>
      <button onClick={() => setRank(rank + 1)}>Add Rank</button>
      <h5>{sum}</h5>
      <button onClick={() => setCount(count + 1)}>Add Count</button>
    </>
  );
}

export default Index;
useRef

useRef 用来获取元素DOM节点,这里还有其他的方法

  1. 直接在节点中获取ref={node => this.node = node}
  2. 使用React.createRef()API
import React, { useRef } from 'react';

function Index() {
  const node = useRef(null);
  const getNode = () => {
    //   打印节点
    console.log(node.current);
  };
  return (
    <div>
      {/* 注意这里要绑定 */}
      <h1 ref={node}>Node</h1>
      <button onClick={() => getNode()}>Get Node</button>
    </div>
  );
}

export default Index;
useImperativeHandle

useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值,下面我们看个父组件调用子组件的focus函数

import React, { forwardRef, useImperativeHandle, useRef } from 'react';

function Input(props, ref) {
  console.log(props, ref);
  const inputRef = useRef(null);


  useImperativeHandle(
    ref,
    () => {
      const handleFn = {
        focus() {
          inputRef.current.focus();
        },
      };
      return handleFn;
    },
    []
  );

  return <input type='text' ref={inputRef} />;
}

const UseInput = forwardRef(Input);

function Index() {
  const current = useRef(null);
  //   console.log(current);
  const handleClick = () => {
    console.log(current);
    const { focus } = current.current;
    focus();
  };

  return (
    <div>
      <UseInput ref={current} />
      <button onClick={handleClick}>获取焦点</button>
    </div>
  );
}

export default Index;

在上述的示例中,React 会将 <UseInput ref={current}> 元素的 current 作为第二个参数传递给 React.forwardRef 函数中的渲染函数 Input。Input会将 current 传递给 useImperativeHandle 的第一个参数 ref。注意:这里就没将current传递给给<input>了,而是用了inputRef来取<input>的DOM实例在useImperativeHandle里面操作,我的理解就是相当于官方不推荐让我们直接操作 input。 而是使用了 useImperativeHandle 来代理操作而已。由 useImperativeHandle 暴露出方法供我们使用。

useLayoutEffect

其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。也就是说useLayoutEffect回调执行在浏览器绘制之前,也就是 useLayoutEffect 回调函数代码可能会阻止浏览器渲染。

import React, { useEffect, useLayoutEffect, useState } from 'react';

function Index() {
  const [color, setColor] = useState(0);

  useLayoutEffect(() => {
    console.log('render');
    if (color === 0) {
      setColor(color + 1);
    }
  }, [color]);

  const colorStyle = {
    color: color ? 'yellow' : 'red',
  };
  return (
    <>
      <div style={colorStyle}>color text {color}</div>
      <button onClick={() => setColor(0)}>Click</button>
    </>
  );
}

export default Index;

从例子我们可以看到,当点击Click按钮的时候,视图会一直显示黄色,因为useLayoutEffect回调阻塞了浏览器的渲染。只有当回调代码执行完了之后,也就是setColor(color + 1)执行后,浏览器才会渲染,看到的视图也就一直是黄色了,当我们把useLayoutEffect换成useEffect的时候,我们快速点击Click按钮的时候就会出现闪烁现象,这是因为useEffect不会阻塞浏览器的渲染,点击按钮执行 serColor(0)后,视图就直接更新了变为红色。然后useEffect回调执行setColor(color + 1)之后,再变为黄色,所以就会出现闪烁现象。

useDebugValue
useDebugValue(value)

useDebugValue 可用于在 React 开发者工具中显示自定义 hook 的标签

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  // ...

  // 在开发者工具中的这个 Hook 旁边显示标签
  // e.g. "FriendStatus: Online"
  useDebugValue(isOnline ? 'Online' : 'Offline');

  return isOnline;
}

提示

我们不推荐你向每个自定义 Hook 添加 debug 值。当它作为共享库的一部分时才最有价值。

useTransition

这个API处于试验阶段,等正式更新后再打算。

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

推荐阅读更多精彩内容