记录一下 React Hooks

作者: Jeremy_young | 来源:发表于2019-12-21 22:21 被阅读0次

大概不久就是各种 hooks 漫天飞舞的世界。

本文记录一下 React Hooks 学习,算是还一下技术债。只是一些形式化的理解的整理,不求甚解。

State Hooks

import { useState } from 'react';
function ExampleWithManyStates() {
  // 声明多个 state 变量
  const [age, setAge] = useState(42);
  const [fruit, setFruit] = useState('banana');
  const [todos, setTodos] = useState([{ text: '学习 Hook' }]);
  const [name, setName] = useState(() => 'jeremy'); // 允许传一个函数来初始化状态
  return <Component {/* ...props */} />
}

useState() 方法就是管理状态state的一个钩子。每次都能在库存中钩出一对值和改值器,即一个状态值,一个修改该状态值的方法。

想象一下,如果 Hooks 机制能在ExampleWithManyStates组件调用之外做好状态的持久化,那么不难理解每次update该组件(即重新调用该组件函数),状态数据能够累计或递增维持了。

有木有很像我们以前写的辛辛苦苦写的一大堆的 reducerreducer 有一个初始状态 initState 和更改对应状态的 update方法。初始状态或者update后的状态是维护在一个全局的store中的,这个store就是起到一个全局状态持久化的作用。

将上面切片式的取状态的写法,做一下退化还原,退化成之前的 class 组件。

import { useState } from 'react';
class ExampleWithManyStates() {
  constructor() {
    // 声明一个 state 变量
    this.state = {
      age: 42,
      fruit: 'banana',
      todos: [{ text: '学习 Hook' }]
    }
  }
  setAge(age) {
    this.setState({age: age})
  }
  setFruit(fruit) {
    this.setState({fruit: fruit})
  }
  addTodo(todo) {
    this.setState({todos: [...this.state.todos, todo]})
  }
  deleteTodo(index) {
    const {todos} = this.state;
    this.setState({
      todos: [...todos.slice(0, index), ...todos.slice(index+1)]
    })
  }
  // .... other codes
}

写类似setAge(age) {this.setState({age: age}) }这样的代码相当乏味,而且在class组件状态很多的时候,无可避免的重复着既乏味又占代码的写法(当然这种简洁的钩子写法没有之前,只会心里莫名犯嘀咕,知道哪里不对劲也使不上劲儿)。

对比新旧组件写法,不难发现2点(实际上只是一点 / 🤦‍♀️):
1、通过 useState() 这样的钩子,React 将这些样板代码揉进自己的框架之中,减少了用户样板代码冗余。
2、代码更紧凑了,用一个钩子方法,就将这些class实例方法全部拍平到一个函数中。

函数组件被多处复用,其状态如何持久化?

那么更多的问题来了,对于一个函数组件,若是被包裹了一次,或许不难理解在函数调用之外,用缓存这个持久化方案解决其函数内部状态问题,这个例子的这一段:手动模拟更新还原过程 写的很清楚。但是若这个组件被多处复用,又该如何做对应位置的状态持久化呢?

// sorry todo

useEffect

数据获取request,设置订阅(事件监听)、手动更改 React 组件中的 DOM 都属于副作用。但是不是一定所有副作用非得放在useEffect 钩子里执行,不一定,但可能不是一个好习惯~

先不纠结什么是副作用,从形式上,可以把 useEffect Hook 看做是 componentDidMountcomponentDidUpdatecomponentWillUnmount 这三个函数的组合。useEffect中的方法,是在 renderDOM 后再执行的,好比于class组件中执行完render后再执行componentDidMount方法一样。

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

function Counter() {
    const [count, setCount] = useState(0);
    useEffect(() => {
      document.title = `Clicked ${count} times`;
      console.log('2');
      return () => {
        // 下次组件被更新,则此尾调会在`2`之前先执行,表示一次清除动作
        console.log('3');
      }
    });
    console.log('1');
    return (
      <div>
        <p>clicked {count} times</p>
        <button onClick={() => setCount(count + 1)}>
          Click me
        </button>
      </div>
    );
}
export default Counter;

以上代码更像是componentDidUpdate,因为useEffect里的方法,会在组件第一次渲染之后和每次组件更新都会执行一次。

小心设置依赖数组

很多时候,我们只需执行一次,即等同于componentDidMount;也有时候,需要有条件的执行,怎么办呢?汇总一下。
1、仅执行一次。给useEffect多传一个空数组[],比如:

useEffect(() => {
      document.title = `Clicked ${count} times`;
}, []);

2、选择性的执行。给useEffect多传一个[count],比如:

useEffect(() => {
      document.title = `Clicked ${count} times`;
}, [count]);

数组参数前面的方法,是否执行,依赖于数组的值前后两次是否变化。
3、每次刷新都执行一遍。不传任何参数,比如:

useEffect(() => {
      document.title = `Clicked ${count} times`;
});

知道了传与不传的区别,但知道传什么似乎更重要。又有两点须知:
1、每次刷新完毕后,都会执行useEffect方法。而useEffect每次执行都会传入一个新的匿名函数,保证了函数中所需变量都是新的,不受闭包影响。所以useEffect的第一个参数方法中,直接找外部拿参数变量即可。
2、useEffect依赖数组中的值,并不会作为前面函数的参数空间。

但到底该传递什么依赖呢?插播一段 官方说明

只有 当函数(以及它所调用的函数)不引用 props、state 以及由它们衍生而来的值时,你才能放心地把它们从依赖列表中省略。

function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const id = setInterval(() => {
      setCount(count + 1); // 这个 effect 依赖于 `count` state
    }, 1000);
    return () => clearInterval(id);
  }, []); // 🔴 Bug: `count` 没有被指定为依赖

  useEffect(() => {
    const id = setInterval(() => {
      setCount(count + 1); // 这个 effect 依赖于 `count` state
    }, 1000);
    return () => clearInterval(id);
  }, [count]); // 🔴 Bug: useEffect 下一次执行总会先把定时器移除

  useEffect(() => {
    const id = setInterval(() => {
      setCount(c => c + 1); // ✅ 在这不依赖于外部的 `count` 变量
    }, 1000);
    return () => clearInterval(id);
  }, []); // ✅ 我们的 effect 不适用组件作用域中的任何变量

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

shouldComponentUpdate 生命周期,该如何实现呢?请看我该如何实现 shouldComponentUpdate?
componentWillReceiveProps 生命周期,该如何实现呢?

useContext

借助React.createContext 和 useContext(),我们拥有了一种 “透传” 的的能力,能将顶层的属性,一次传递到任意子层级的组件,而不需要层层接力式的传递。

这个钩子,在处理语言intl属性时,简直不能太方便。煮一个官方的栗子:

const ThemeContext = React.createContext(themes.light);
function App() {
  return (
    <ThemeContext.Provider value={themes.dark}>
      <Father>
        <Child />
      </Father>
    </ThemeContext.Provider>
  );
}
const Child = (props) => {
  const theme = useContext(ThemeContext);
  return (
    <button style={{ background: theme.background, color: theme.foreground }}>
      I am styled by theme context!
    </button>
  );
}

useReducer

const [state, dispatch] = useReducer(reducer, initialArg, init);
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>
    </>
  );
}

useCallback

function Foo(props) {
  const [count, setCount] = useState(0);
  return <Button onClick={() => props.handleVisible(false)}>Click Me</Button>;
}

// 优化一下
function Foo(props) {
  const [count, setCount] = useState(0);
  // 这样只创建一次回调函数
  const handleVisible = useCallback(e => {
    props.handleVisible(false);
  }, [])
  return <Button onClick={handleVisible}>Click Me</Button>;
}

//
function Foo(props) {
  const [count, setCount] = useState(0);
  const reportCount = useCallback( count => e => {
    props.reportCount(count);
  },)
  return <Button onClick={reportCount(count)}>Click Me</Button>;
}

useMemo

useMemo 能记忆一个方法执行的结果值,假如下次刷新组件时,依赖不变,则useMemo不会执行这个方法,而是直接拿到上次记忆的值。
如果这个方法是个耗时运算,或是返回一个组件,当依赖不变,就直接拿记忆值,这样就能起到性能优化的效果。

const long_time_result = useMemo(() => {
    const result = long_time_function(count);
    return result;
  }, [count]) 

useRef

是用对象引用方式,用户代码可以用它来做一般数据的缓存。说白了还是一种持久化。

so 和useState 有什么差别呢?唯一差别是:useState多返回了一个可以刷新本组件的方法,而useRef单纯提供一个引用访问链,你组件再怎么重复刷新,这个引用链也不会断掉。

这个钩子,在组件两次刷新之间,因为会产生两帧独立的闭包,所以用它来保持数据关联性就非常合适了。

React.memo

function MyComponent(props) {
  // render using props
}
function areEqual(prevProps, nextProps) {
  // return true if passing nextProps to render would return
  // the same result as passing prevProps to render,
  // otherwise return false
}
export default React.memo(MyComponent, areEqual);

总结一下

1、useStateuseRef钩子行为相似。
2、useContext具有透传能力
3、其他钩子在于依赖。
4、捕获值的这个特性是我们写钩子最最需要注意的问题,它是函数特有的一种特性,并非函数式组件专有。函数的每一次调用,会产生一个属于那一次调用的作用域,不同的作用域之间不受影响。

其他

react-redux的钩子

状态管理方面,React 社区最有名的工具当然是 Redux。在 react-redux@7.1 中新引用了三个 API:

  • useSelector。它有点像 connect() 函数的第一个参数 mapStateToProps,把数据从 state 中取出来;
  • useStore 。返回 store 本身;
  • useDispatch。返回 store.dispatch。

关于测试

觉得还是到改了一部分 hooks 写法后,在加单元测试。现在堆积了很多逻辑的 class 组件真心难写。如何测试使用了 Hook 的组件4

相关文章

  • 记录一下 React Hooks

    大概不久就是各种 hooks 漫天飞舞的世界。变天了。 本文记录一下 React Hooks 学习,算是还一下技术...

  • 十个案例学会 React Hooks

    在学会使用React Hooks之前,可以先看一下相关原理学习React Hooks 前言 在 React 的世界...

  • React Hooks编码示例视频教程

    之前做了一次关于React Hooks的直播分享,却忘记录屏了。。。这次重新录了放出来: React Hooks ...

  • React Hooks用法详解(二)

    React Hooks 在了解React Hooks之前, 我们先看一下 Class 和函数式 的一般写法 Cla...

  • React Hooks

    React Hooks Hooks其实就是有状态的函数式组件。 React Hooks让React的成本降低了很多...

  • react-hooks

    前置 学习面试视频 总结react hooks react-hooks react-hooks为函数组件提供了一些...

  • React Hooks

    前言 React Conf 2018 上 React 提出了关于 React Hooks 的提案,Hooks 作为...

  • 初识React Hooks

    最近在新项目中用到了react hooks,趁热乎总结一下。 1. React Hooks是什么&为什么要用Rea...

  • 5分钟简单了解React-Hooks

    首先附上官网正文?:React Hooks Hooks are a new addition in React 1...

  • react-hooks

    react-hooks react-hooks 是react16.8以后,react新增的钩子API,目的是增加代...

网友评论

    本文标题:记录一下 React Hooks

    本文链接:https://www.haomeiwen.com/subject/lduhnctx.html