React Hooks的学习笔记

"Unlearn what you have learned" -- Yodajavascript

前言

有一天我在逛Medium的时候,忽然发现了一篇介绍React Hooks的文章,我认真看了一遍后,计划好好了解一下它。html

在学习Hooks以前,官网上说了Hooks是彻底可用的(v16.8.0),并无破坏性变动,并且彻底向后兼容,与其说是一种新API,不如说是React Team他们把React更核心的操做数据与UI的能力挖掘了出来。java

嗯美滋滋~学完应该能够在工做项目里用了!开始学习吧!react

Hooks的起步使用

其实Hooks主要经常使用的能够有如下几个:redux

  • useState
  • useEffect
  • useContext
  • useMemo
  • useRef
  • useReducer
  • useCallback

列举的以上这几个,其实已经算是比较经常使用的,尤为是前两个,接下来就会介绍它们部分几个的使用。数组

useState

useState这个钩子其实对应的是咱们以前class Component里的this.setState缓存

  1. useState传参表明默认值,能够是原始值,也能够是对象、数组,因此其实表达能力很丰富。
  2. useState调用后返回是一对值,对应当前的值更新这个值的函数,用数组解构的方式获取很简洁。
  3. useState在一个函数组件里能够屡次使用。
  4. useStatethis.setState区别之处在于,前者每次更新后state都是新值,换而言之实际上是不可变数据的概念。然后者使用后,其实更新state部分的值,引用自己并没有改变。

简单使用以下示例。bash

import React, { useState } from 'react';

export default function StateHook() {
  const [count, useCount] = useState(0);
  return (
    <> <p>You clicked {count} times</p> <button onClick={() => useCount(count + 1)}>Click me</button> </> ); } 复制代码

useEffect

useEffect这个钩子势必是咱们经常使用的。架构

  1. 它基本能够等价于componentDidMountcomponentDidUpdate的这两个生命周期钩子组合的效果。那么它的调用时机大概是每次渲染结束后,因此不会阻塞组件渲染。
  2. useEffect通常用于实现设置数据请求、监听器等有反作用的功能,传入的第一个参数函数A1用于设置反作用,而是传入的这个函数能够返回一个函数A2用于取消函数A1的反作用。这两个函数的React调用它们时机分别在于,注册反作用的函数A1在当次渲染结束后当即执行,取消反作用的函数A2在下次渲染开始以前当即执行。再次强调,这么设计的理由仍是为了避免阻塞组件渲染。
  3. useEffect第二个参数用于设置反作用的依赖数组。什么意思?思惟灵活的同窗已经想到了,若是每次渲染都执行反作用,有可能形成性能浪费,那么能够经过告诉React,这个钩子依赖某些props或者states,在这些依赖不发生改变时,这个反作用不会再重复执行。在如下的例子中,能够传空数组,告诉React该反作用什么也不依赖,那么它只会在第一次渲染时执行一次(可是通常不推荐这么作)。若是不传第二个参数,则意味着每次渲染都必然执行一次,此时应当注意内存泄露。
  4. 同窗们有没有发现,使用useEffect后,一个反作用的注册监听与对应的取消注册逻辑所有放在了一块儿,对比与以往的分别在componentDidMountcomponentDidUpdatecomponentWillUnmount里分散同一反作用的逻辑。useEffect的使用更有吸引力和说服力了。
import React, { useState, useEffect } from 'react';

export default function EffectHook({ dep }) {
  const [width, setWidth] = useState(window.innerWidth);
  
  function handleWindowResize() {
    const w = window.innerWidth;
    setWidth(w);
  }
  
  useEffect(() => {
    window.addEventListener('resize', handleWindowResize);
    return () => {
      window.removeEventListener('resize', handleWindowResize);
    };
  }, 
    // deps
    []
  );

  return (
    <> <p>window.innerWidth: {width}</p> </> ); } 复制代码

useContext

这个钩子仍是和原有的Context.ProviderContext.Consumer同样的理解便可。用法示例以下,理解方便,再也不赘述。app

import React, { useContext } from 'react';

export const souliz = {
  name: 'souliz',
  description: 'A normal human named by his cat.'
};

export const UserContext = React.createContext(souliz);

export default function ContextHook() {
  const context = useContext(UserContext);

  return (
    <>
      <p>UserContext name: {context.name}</p>
      <p>UserContext description: {context.description}</p>
    </>
  );
}
复制代码

useMemo

有时候咱们会遇到一个极耗性能的函数方法,但因为依赖了函数组件里一些状态值,又不得不放在其中。那么若是咱们每次渲染都去重复调用的发,组件的渲染必然会十分卡顿。

所以写了如下示例验证,一个计算斐波那契的函数(众所周知的慢),读者能够拷贝这段代码,注释useMemo那一行,使用直接计算来看,点击按钮触发组件从新渲染,会发现很卡顿(固然了),那么此时useMemo做用就发挥出来了,其实理解上仍是和原有的React.memo同样,可用于缓存一下计算缓慢的函数,若是依赖没有发生改变,则重复使用旧值。前提必然是这个函数是一个纯函数,不然必然会引起问题。

useCallback其实也和useMemo道理相似,不过它解决的问题其实若是依赖不改变,使用旧的函数引用,在useEffect的依赖是函数时,可使用useCallback的特性来避免重复触发反作用的发生,所以再也不赘述useCallback

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

let fib = n => (n > 1 ? fib(n - 1) + fib(n - 2) : n);
let renders = 0;

export default function MemoHook() {
  const defaultInput = 37;
  const [input, setInput] = useState(defaultInput);
  const [time, setTime] = useState(0);
  const value = useMemo(() => fib(input), [input]);
  // 来来来,看看不使用Memo的后果就是卡顿
  // const value = fib(input);

  return (
    <>
      <p>fib value is {value}</p>
      <input
        type="number"
        value={input}
        onChange={e => setInput(e.target.value)}
      />
      <button onClick={() => setTime(time + 1)}>Trigger render {time}</button>
      <footer>render times: {renders++}</footer>
    </>
  );
}
复制代码

useRef

useRef这个钩子须要更通用的理解方式,不一样于咱们以前使用的React.createRef(),这个钩子用于建立的是一个引用对象,那么能够用于突破useState所带来的局限。什么意思呢?useState每次渲染都是新的值,也就是下面示例中,若是我点击3次按钮,分别更新了值触发5次组件从新渲染,那么经过延时5秒后获取current值如示例二,若是须要在某些操做中获取组件最新的某些state是最新的值的时候,useRef能够派上大用场。

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

export default function RefHook() {
  const [count, setCount] = useState(0);
  const latestCount = useRef(count);

  latestCount.current = count;
  useEffect(() => {
    setTimeout(() => {
      console.log(`Ref: You clicked ${latestCount.current} times`);
      console.log(`state: You clicked ${count} times`);
    }, 5000);
  });

  return (
    <> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}>Click me</button> </> ); } 复制代码
Ref: You clicked 3 times
state: You clicked 1 times
Ref: You clicked 3 times
state: You clicked 2 times
Ref: You clicked 3 times
state: You clicked 3 times
复制代码

useReducer

相信同窗们都使用过redux,React Team考虑到这种使用方式常见,因而设计出来了这么一个钩子。这样的话其实解决了咱们常见写redux的多文件跳跃编写的烦恼,并且十分易于理解。(固然还有比较高级的用法)。如下代码示例。

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

const defaultTodos = [
  {
    id: 1,
    text: 'Todo 1',
    completed: false
  },
  {
    id: 2,
    text: 'Todo 2',
    completed: false
  }
];

function todosReducer(state, action) {
  switch (action.type) {
    case 'add':
      return [
        ...state,
        {
          id:  Date.now(),
          text: action.text,
          completed: false
        }
      ];
    case 'complete':
      return state.map(todo => {
        if (todo.id === action.id) {
          todo.completed = true;
        }
        return todo;
      });
    default:
      return state;
  }
}

export default function ReducerHook() {
  const [todos, dispatch] = useReducer(todosReducer, defaultTodos);
  const [value, setValue] = useState('');

  function handleTextChange(e) {
    setValue(e.target.value);
  }

  function handleAddTodo() {
    if (value === '') {
      return;
    }
    dispatch({
      type: 'add',
      text: value
    });
    setValue('');
  }

  function handleCompleteTodo(id) {
    dispatch({
      type: 'complete',
      id
    });
  }

  return (
    <>
      <section>
        <input
          type="text"
          onChange={handleTextChange}
          value={value}
        />
        <button onClick={handleAddTodo}>Add Todo</button>
      </section>
      <ul className="todos">
        {todos.map(todo => (
          <ol id={todo.id} key={todo.id}>
            <span
              style={{
                textDecoration: todo.completed ? 'line-through' : 'none'
              }}
            >
              {todo.text}
            </span>
            <input
              type="checkbox"
              disabled={todo.completed}
              onClick={() => handleCompleteTodo(todo.id)}
            />
          </ol>
        ))}
      </ul>
    </>
  );
}
复制代码

其实useReducer的原理大概也能够这么来实现。

function useReducer(reducer, initialState) {
  const [state, setState] = useState(initialState);

  function dispatch(action) {
    const nextState = reducer(state, action);
    setState(nextState);
  }

  return [state, dispatch];
}
复制代码

相信学完这些Hooks的使用后,许多同窗都是心里充满了不少疑惑的同时也想要尝试看看怎么使用到实际项目了。

固然如今React官方的建议是:

  • 能够小规模的使用了,可是无需重写之前的组件实现。React是不会移除class Component这些原有API的。
  • 若是决定使用Hooks的话,能够加上React提供的eslint-plugin-react-hooks,用于检测对于Hooks的不正当使用。(据说create-react-app很快将会加上这个配置)
  • 学习与使用React Hooks其实更须要的是换一种心智模型去理解,Hooks更多的像是一个同步处理数据的过程。

Hooks存在的意义以及缘由?

传统组件的开发有如下几个局限:

  1. 难以复用含有state(状态)的组件逻辑。HOC、render props这两种作法虽然能够解决,可是一是须要从新架构组件,可能会使代码更复杂。二是可能会形成wrapper hell。
  2. 复杂组件难以理解消化。由于状态逻辑、消息订阅、请求、以及反作用在不一样的生命钩子混乱穿插,彼此耦合,使得一个组件难以再细化拆分。即便使用了Redux这种状态管理的库后,也引进了更高层的抽象,同时须要在不一样的文件之间穿插跳跃,复用组件也不是一件容易的事。
  3. class让人困惑。(先别急着反对)一个是this让人困惑,经常须要绑定、第二是class转译和压缩出来的代码其实至关冗长。

Hooks的注意事项

  1. 只能在函数的顶层使用,不能嵌套于循环体、判断条件等里面。缘由是由于须要确保Hooks每次在组件渲染中都是按照一样的顺序,这个十分重要,具体缘由将会是一个很大的篇幅
  2. 只能在React函数组件里,或者自定义钩子(custom Hooks)里使用。

总结

写到这里,文章篇幅已经很长了。一篇文章是说不完Hooks的。学习Hooks的最推荐的实际上是看官网文档以及Dan Abramov的博文,以及多多动手实践。

谢谢你们阅读~~

相关文章
相关标签/搜索