今天看啥  ›  专栏  ›  树街猫UP

react hooks 初步使用(一)

树街猫UP  · 掘金  ·  · 2021-04-16 18:41
阅读 41

react hooks 初步使用(一)

一、前言

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

《黄金时代》王小波

二、关于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处于试验阶段,等正式更新后再打算。




原文地址:访问原文地址
快照地址: 访问文章快照