一、React 的两套 API

以前,React API 只有一套,现在有两套:类(class)API 和基于函数的钩子(hooks) API。

任何一个组件,可以用类来写,也可以用钩子来写。下面是类的写法。

class Hello extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}!</h1>;
  }
}

再来看钩子的写法,也就是函数。

function Hello(props) {
  return <h1>Hello, {props.name}!</h1>;
}

这两种写法,作用完全一样。很多人自然会问:"我应该使用哪一套 API?"

官方推荐使用钩子(函数),而不是类。因为钩子更简洁,代码量少,用起来比较"轻",而类比较"重"。而且,钩子是函数,更符合 React 函数式的本质。

但是,钩子的灵活性太大,很多人不太容易理解。很多人一知半解,很容易写出混乱不堪、无法维护的代码。那就不如使用类了。因为类有很多强制的语法约束,不容易搞乱。

二、类和函数的差异

严格地说,类组件和函数组件是有差异的。不同的写法,代表了不同的编程方法论。

类(class)是数据和逻辑的封装。 也就是说,组件的状态和操作方法是封装在一起的。如果选择了类的写法,就应该把相关的数据和操作,都写在同一个 class 里面。

函数一般来说,只应该做一件事,就是返回一个值。 如果你有多个操作,每个操作应该写成一个单独的函数。而且,数据的状态应该与操作方法分离。根据这种理念,React 的函数组件只应该做一件事情:返回组件的 HTML 代码,而没有其他的功能。

还是以上面的函数组件为例。

function Hello(props) {
  return <h1>Hello, {props.name}!</h1>;
}

这个函数只做一件事,就是根据输入的参数,返回组件的 HTML 代码。这种只进行单纯的数据计算(换算)的函数,在函数式编程里面称为 "纯函数"(pure function)。

三、副效应是什么?

看到这里,你可能会产生一个疑问:如果纯函数只能进行数据计算,那些不涉及计算的操作(比如生成日志、储存数据、改变应用状态等等)应该写在哪里呢?

函数式编程将那些跟数据计算无关的操作,都称为 "副效应" (side effect) 。如果函数内部直接包含产生副效应的操作,就不再是纯函数了,我们称之为不纯的函数。

纯函数内部只有通过间接的手段(即通过其他函数调用),才能包含副效应。

四、什么是钩子(hook)

说了半天,那么钩子到底是什么?

React Hooks 的意思是,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码"钩"进来。 React Hooks 就是那些钩子。

你需要什么功能,就使用什么钩子。React 默认提供了一些常用钩子,你也可以封装自己的钩子。

所有的钩子都是为函数引入外部功能,所以 React 约定,钩子一律使用use前缀命名,便于识别。你要使用 xxx 功能,钩子就命名为 usexxx。

一句话,钩子(hook)就是 React 函数组件的副效应解决方案,用来为函数组件引入副效应。 函数组件的主体只应该用来返回组件的 HTML 代码,所有的其他操作(副效应)都必须通过钩子引入。

由于副效应非常多,所以钩子有许多种。React 为许多常见的操作(副效应),都提供了专用的钩子。

useState():保存状态
useContext():保存上下文
useRef():保存引用
......

上面这些钩子,都是引入某种特定的副效应,而 useEffect()是通用的副效应钩子 。找不到对应的钩子时,就可以用它。其实,从名字也可以看出来,它跟副效应(side effect)直接相关。

五、注意事项

1、只能在函数内部的最外层调用 Hook,不要在循环、条件判断或者子函数中调用;

2、只能在 React 的函数组件中调用 Hook,不要在其他 JavaScript 函数中调用。

六、常见hook

1、useState

作用:

用于为函数组件引入状态(state)。纯函数不能有状态,所以把状态放在钩子里面。

使用方式:

import React, { useState } from 'react'

// const [state, setState] = React.useState(initialState)

const [stateN, setStateN] = React.useState(0)

React.useState(params) 设置第一个参数的初始值 。

stateN是解构出的第一个参数 。

setStateN是第二个参数,是一个函数用来更新state,并且会触发新的渲染。同时,在后续新的渲染中 React.useState() 返回的第一个 state 值始终是最新的。

可以这么操作 setStateN(stateN + 1),这样 stateN设置称为最新的值。

setXxx()2种写法:

  1. setXxx(newValue):参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值;
  2. setXxx(value => newValue):参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值。

为了保证 memoizedState 的顺序与 React.useState() 正确对应,我们需要保证 Hooks 在最顶层调用,也就是不能在循环、条件或嵌套函数中调用。

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

React.useState() 通过 Object.is() 来判断 memoizedState 是否需要更新。

示例:


import React, {useState} from 'react';
import ReactDom from 'react-dom'
 
const App = ()=>{
    console.log("useState test")
    const [count,setCount] = useState(0)
    const add = ()=>{
        setCount(count+1)
    }
    return (
        <div>
            <div>{count}</div>
            <button onClick={add}>+1</button>
        </div>
    )
}
 
ReactDom.render(<App/>,document.querySelector("#root"))

2、useEffect

作用:

用来引入具有副作用的操作,就是指定一个副效应函数,组件每渲染一次,该函数就自动执行一次。组件首次在网页 DOM 加载后,副效应函数也会执行。(可用于模拟类组件中的生命周期钩子),最常见的就是向服务器请求数据。以前,放在componentDidMount里面的代码,现在可以放在useEffect()。

使用方式:

useEffect(()  =>  {
  // 副作用代码
}, [dependencies])

useEffect 接收两个参数,第一个参数是一个回调函数,表示要执行的副作用操作,这个回调函数将会在组件渲染完成后执行。第二个参数是一个数组,表示副作用操作的依赖项,依赖项数组表示什么情况下需要重新运行 effect

当组件渲染完成后,useEffect 函数中的回调函数会被执行一次。如果依赖数组中的值发生了变化,那么 useEffect 会在组件更新后再次执行回调函数。

  1. 如果第二个参数数组为空,它就是componentDidMount,只有第一次渲染;
  2. 如果第二个参数数组里面有值,它就是componentDidUpdate,只要其中任何一个state改变了,它就重新渲染;
  3. 如果没有第二个数组(只有一个函数),则只要有任何一个state改变,它就重新渲染。

useEffect 函数的作用可以分为以下几个方面:

  1. 执行副作用操作:比如订阅事件、请求数据、更新 DOM 等操作。
  2. 清除副作用:当组件卸载时,可以在 useEffect 函数中返回一个清除函数来清除订阅、定时器等副作用。
  3. 控制 useEffect 的执行时机:通过依赖数组来控制 useEffect 的执行时机,当依赖项发生变化时,useEffect 就会重新执行副作用操作。

需要注意的是,如果 useEffect 函数中没有指定依赖数组,那么 useEffect 函数会在每次组件更新后都执行回调函数,这可能会导致性能问题。

另外,useEffect 函数中的回调函数可以返回一个函数,用于清除副作用。当组件卸载时,React 会自动调用该函数来清除副作用。例如:

useEffect(() => {
  const timerId = setInterval(() => {
    console.log('Tick');
  }, 1000);

  return () => {
    clearInterval(timerId);
  };
}, []);

上面的例子中,当组件渲染完成后,会创建一个定时器,每秒钟输出 'Tick'。在 useEffect 函数中,返回一个清除函数,用于在组件卸载时清除定时器。由于依赖数组为空,useEffect 函数只会在组件渲染完成后执行一次回调函数。

如果第二个参数是一个空数组,就表明副效应参数没有任何依赖项。因此,副效应函数这时只会在组件加载进入 DOM 后执行一次,后面组件重新渲染,就不会再次执行。这很合理,由于副效应不依赖任何变量,所以那些变量无论怎么变,副效应函数的执行结果都不会改变,所以运行一次就够了。

import React, { useState, useEffect } from 'react';
 
function MyComponent() {
  const [data, setData] = useState(null);
 
  useEffect(() => {
    // 在组件渲染完成后执行的副作用操作
    fetchData();
  }, []);
 
  const fetchData = async () => {
    // 异步获取数据的逻辑
    const response = await fetch('https://api.example.com/data');
    const json = await response.json();
    setData(json);
  };
 
  return <div>{data ? <div>Data loaded</div> : <div>Loading...</div>}</div>;
}

可以看到,在上述代码中,我们在组件渲染完成后调用 useEffect,并传入一个空的依赖项数组 []。这意味着 fetchData 函数只会在组件第一次渲染时执行一次。这样可以避免重复获取数据。当依赖项数组中的值发生变化时,useEffect 将会重新运行。 

用途:

只要是副效应,都可以使用useEffect()引入。它的常见用途有下面几种。

  1. 获取数据(data fetching)
  2. 事件监听或订阅(setting up a subscription)
  3. 改变 DOM(changing the DOM)
  4. 输出日志(logging)
  5. 下面是从远程服务器获取数据的例子。
import React, { useState, useEffect } from 'react';
import axios from 'axios';

function App() {
  const [data, setData] = useState({ hits: [] });

  useEffect(() => {
    const fetchData = async () => {
      const result = await axios(
        'https://hn.algolia.com/api/v1/search?query=redux',
      );

      setData(result.data);
    };

    fetchData();
  }, []);

  return (
    <ul>
      {data.hits.map(item => (
        <li key={item.objectID}>
          <a href={item.url}>{item.title}</a>
        </li>
      ))}
    </ul>
  );
}

export default App;

上面例子中,useState()用来生成一个状态变量(data),保存获取的数据;useEffect()的副效应函数内部有一个 async 函数,用来从服务器异步获取数据。拿到数据以后,再用setData()触发组件的重新渲染。

由于获取数据只需要执行一次,所以上例的useEffect()的第二个参数为一个空数组。

useEffect() 的返回值:

副效应是随着组件加载而发生的,那么组件卸载时,可能需要清理这些副效应。

useEffect()允许返回一个函数,在组件卸载时,执行该函数,清理副效应。如果不需要清理副效应,useEffect()就不用返回任何值。

useEffect(() => {
  const subscription = props.source.subscribe();
  return () => {
    subscription.unsubscribe();
  };
}, [props.source]);

上面例子中,useEffect()在组件加载时订阅了一个事件,并且返回一个清理函数,在组件卸载时取消订阅。

实际使用中,由于副效应函数默认是每次渲染都会执行,所以清理函数不仅会在组件卸载时执行一次,每次副效应函数重新执行之前,也会执行一次,用来清理上一次渲染的副效应。

useEffect() 的注意点:

使用useEffect()时,有一点需要注意。如果有多个副效应,应该调用多个useEffect(),而不应该合并写在一起。

function App() {
  const [varA, setVarA] = useState(0);
  const [varB, setVarB] = useState(0);
  useEffect(() => {
    const timeoutA = setTimeout(() => setVarA(varA + 1), 1000);
    const timeoutB = setTimeout(() => setVarB(varB + 2), 2000);

    return () => {
      clearTimeout(timeoutA);
      clearTimeout(timeoutB);
    };
  }, [varA, varB]);

  return <span>{varA}, {varB}</span>;
}

上面的例子是错误的写法,副效应函数里面有两个定时器,它们之间并没有关系,其实是两个不相关的副效应,不应该写在一起。正确的写法是将它们分开写成两个useEffect()。

function App() {
  const [varA, setVarA] = useState(0);
  const [varB, setVarB] = useState(0);

  useEffect(() => {
    const timeout = setTimeout(() => setVarA(varA + 1), 1000);
    return () => clearTimeout(timeout);
  }, [varA]);

  useEffect(() => {
    const timeout = setTimeout(() => setVarB(varB + 2), 2000);

    return () => clearTimeout(timeout);
  }, [varB]);

  return <span>{varA}, {varB}</span>;
}

 useState 和 useEffect  支持写多个,例如:

const [count, setCount] = useState(0);
const [count2, setCount2] = useState(0);
 
useEffect(() => {
 
},[])
 
useEffect(() => {
 
},[xxx])

3、useMemo

作用:

写在函数式组件里的 “函数调用代码”。如果函数式组件重新渲染时,每次都会执行“调用函数的代码”。如果不是必须的,那么就是性能的浪费。useMemo就是解决这个问题的。即:useMemo是防止不必要的的函数调用。

useMemo 解决的是 防止函数式组件里的 “调用函数的代码” 被多次被执行。既就是:useMemo 是保证函数式组件重新渲染时,组件里的“函数调用代码” 的执行时可控的。

使用方式:

useMemo(fn, arr); //当数组中的其中一个元素,发生变化时,就会调用 函数 。
const nameStr =  useMemo(() => genName(name),[name]) 
// 表示,当name发生变化时,才会调用 () => genName(name)函数

特点

接受两个参数,第一个参数为()=>value;

第二个参数是依赖,为[m,n];

只有当依赖变化时,才会计算新的value;

如果依赖不变,那么就会重用之前的value;

就像Vue2中的计算属性computed一样。

示例

以下代码中,如果不使用useMemo,当我们点击“修改name”的按钮时,也调用了函数isAdult()。这其实是性能的损耗。如果加上了useMemo。那么,只有点击“修改age” 按钮时,才会调用isAdult(),因为,我的依赖写的是age。

//父组件:ParentFn.jsx
import { useState } from "react";
import SonFn from "./Son";

export default () => {
    console.log("父组件");

    const [name, setName] = useState('Sam')
    const [age, setAge] = useState(12)

    return (
        <>
            <h1>useMemo</h1>
            <button
                onClick={() => setName(name + "1")}>修改name</button>
            <button
                onClick={() => setAge(age + 1)}>修改age</button>
            <hr />
            <SonFn name={name} age={age} />
        </>
    )
}

//子组件:SonFn.jsx
import React, { memo, useMemo } from 'react'

const SonFn = ({ name, age }) => {
    console.log("子组件");

    function isAdult(age1) {
        return age1 >= 18 ? "已成年" : "未成年";
    }

    // 写在函数式组件里的 “函数调用代码” 。
    // 只要函数式组件重新渲染了,那么isAdult函数就会被调用一次。即使只是改了name的值。
    // let adultStr = isAdult(age);

    //现在,加上useMemo后,表示只有age发生变化,才调用isAdult函数。
    let adultStr = useMemo(() => {
        console.log("age更新了才会执行")
        return isAdult(age);
    }, [age]);

    return (
        <div>
            <h5>子组件(函数式组件)</h5>
            <p>姓名:{name}</p>
            <p>年龄:{age}</p>
            <p>是否成年:{adultStr}</p>
        </div>
    )
}

export default memo(SonFn);

注意事项

如果value是一个函数,那这个Hook就要写成下面这种看着都麻烦的形式:

useMemo( ( ) => (x) => console.log( x ))

这是一个返回函数的函数。

因为不好用,于是React的开发团队又添加了一个useCallback。

4、useCallback

useCallback(x=>console.log(x), [m]) 等价于 useMemo(()=>x=>console.log(x),[m])

useCallback和useMemo的区别:

1、useMemo:解决的是:防止无效函数调用

2、useCallback:解决的是:防止无效函数定义

示例:

import React, { memo, useCallback, useState } from "react";
 
const HookTest = memo(() => {
  const [count, setcount] = useState(0);
  const [num, setnum] = useState(100);
  const showCount = () => {
    console.log("useCallback test,", count + "$");
  };
  return (
    <div>
      <h2>HookTest:useCallback----useMemo</h2>
      <h3>useCallBack</h3>
      <h4>
        count:{count}---num:{num}
      </h4>
      <button onClick={showCount}>showCount</button>
      <button onClick={(e) => setcount(count + 3)}>+3</button>
      <button onClick={(e) => setnum(num * 10)}>*10</button>
  );
});
 
export default HookTest;

正常执行的话,修改count 和 num 都会触发 render ,这是毫无疑问的。那么showCount 每次render也会被重新创建一次,这也是情理之中的。 但是我们想想,真的有必要每次render都重新创建showCount 吗?现在可能只有一个函数,函数体也简单,重新创建也影响不了什么,但是真正开发的时候却是我们要考虑的问题。 所以 useCallback 来帮我们解决这个问题了! 

const showCount = () => {
   console.log("useCallback test", count + "$");
};
  
const showCount = useCallback(() => {
   console.log("useCallback test", count + "$");
}, [count]);

那么现在的 showCount 和之前的又有什么不一样呢?

现在的 showCount 是被缓存起来了,组件render时,如果装载依赖的数组中的依赖未更新,那么依然采用缓存的函数。也就是说只有当我点修改count时触发更新组件render后,showCount 也重新创建,但是当我进行其他的操作引起组件render时,由于此时条件依赖跟装载依赖的数组中依赖毫无关系,showCount 用的是缓存里的函数。

5、useRef

作用:

可以在函数组件中存储/查找组件内的标签或任意其它数据,功能与React.createRef()一样。如果你需要一个值,在组件不断render的过程中保持不变(永远都是同一个n,而不是说值不变),那么你就需要使用useRef。

使用方式:

const refContainer = useRef(initialValue);

useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内持续存在。 

一个常见的用例便是命令式地访问子组件:

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

本质上,useRef 就像是可以在其 .current 属性中保存一个可变值的“盒子”。

你应该熟悉 ref 这一种访问 DOM 的主要方式。如果你将 ref 对象以 <div ref={myRef} /> 形式传入组件,则无论该节点如何改变,React 都会将 ref 对象的 .current 属性设置为相应的 DOM 节点。

然而,useRef() 比 ref 属性更有用。它可以很方便地保存任何可变值,其类似于在 class 中使用实例字段的方式。

这是因为它创建的是一个普通 Javascript 对象。而 useRef() 和自建一个 {current: ...} 对象的唯一区别是,useRef 会在每次渲染时返回同一个 ref 对象。

请记住,当 ref 对象内容发生变化时,useRef 并不会通知你。变更 .current 属性不会引发组件重新渲染。如果想要在 React 绑定或解绑 DOM 节点的 ref 时运行某些代码,则需要使用回调 ref 来实现。

比较:

useState/useReducer:n每次都会变(都是不同的变量n);
useMemo/useCallback : 只有依赖的[m]变的时候,fn才会变;
useRef : 永远不变 。

与vue3的ref相比,vue3中

初始化:const count = ref(0);
读取:count.value;
不同点:当count.value变化时,Vue3会自动render

6、useContext

作用:

解决组件间传值的问题,相较于父子组件的props传值,useContext一般用于多层级传值。多级传递,不限于2级的关系,而是可以在嵌套组件中,多层级传递。

使用场景:

一般用于根组件 传多语言配置修改,主题类型修改等等。

示例:

// App.jsx

/* 
    1.需要引入createContext,useContext
    2.通过createContext来创建句柄
    3.Context.Provider来确定共享范围
    4.通过value来分发内容
    5.在子组件中通过useContext(Context句柄)来获取数据
*/

import React, { useState, createContext, useContext } from "react";

const C = createContext(null);

function App() {
  console.log("App 执行了");
  const [n, setN] = useState(0);
  return (
    <C.Provider value={{ n, setN }}>
      <div className="App">
        <Father />
      </div>
    </C.Provider>
  );
}

function Father() {
  const { n, setN } = useContext(C);
  return (
    <div>
      我是Father n: {n} <Child />
    </div>
  );
}

function Child() {
  const { n, setN } = useContext(C);
  const onClick = () => {
    setN((i) => i + 1);
  };
  return (
    <div>
      我是Son 我得到的 n: {n}
      <button onClick={onClick}>+1</button>
    </div>
  );
}

export default App;

 7、useReducer

作用:

useReducer 是 useState 的升级版本,用来践行 Flux/Redux 的思想,它主要有两个参数,一个读接口 state,一个写接口 dispatch,要自己先定义好 state 状态量和 setState 的一些函数 API。

使用方式:

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

initialArg为初始数据;

useReducer返回一个数组,包含state,dispath;

action为判断事件类型,通过dispatch传递;

示例1:

import React, { useReducer } from 'react';

const App = () => {
    const [state, dispath] = useReducer((state, action) => {
        console.log(state);
        switch (action.type) {
            case 'increment':
                return state + 1;
            case 'decrement':
                return state - 1;
            default:
                return state;
        }
    }, 0);

    return (
        <div className='App'>
            <button onClick={() => dispath({ type: 'increment' })}>increment</button>
            <button onClick={() => dispath({ type: 'decrement' })}>decrement</button>
            <p>{state}</p>
        </div>
    );
};

export default App;

示例2:

在页面中显示 n 的值,且按下按钮后会触发响应的操作使 n 值发生变化。其中的操作分别是使 n+1,n+2,nx2。

const initialState = {
  n: 0,
};
const reducer = (state, action) => {
  if (action.type === "add") {
    /* 规则与useState一样必须返回新的对象,不然变量值不会改变 */
    return { n: state.n + action.number };
  } else if (action.type === "multi") {
    return { n: state.n * action.number };
  } else {
    throw new Error("unknown type!");
  }
};
/* 在函数组件中使用useReducer */
const App = () => {
  const [state, dispatch] = useReducer(reducer, initialState);
  const onclick1 = () => {
    dispatch({ type: "add", number: 1 });
  };
  const onclick2 = () => {
    dispatch({ type: "add", number: 2 });
  };
  const onclick3 = () => {
    dispatch({ type: "multi", number: 2 });
  };
  return (
    <div className="App">
      <h1>n:{state.n}</h1>
      <button onClick={onclick1}>+1</button>
      <button onClick={onclick2}>+2</button>
      <button onClick={onclick3}>x2</button>
    </div>
  );
};
ReactDOM.render(<App />, document.getElementById("root"));

 

用法与 useState 类似,从 useReducer 中得到读接口 state,写接口 dispatch。最后操作时传参给 dispatch 写接口。操作灵活多变,比 useState 好处就是能聚集所有的操作和各种状态量。着重理解这几行代码,读懂。

useReducer 算是 useState 的复杂版。

使用 useReducer 分以下步骤:

  1. 创建初始值的状态initialState
  2. 创建所有对状态的操作reducer(state,action)
  3. 传给useReducer,得到读和写的接口
  4. 调用写({'type':'操作类型'})

示例3:使用 useReducer 写一个表单提交

写一个简单的表单,包含姓名,年龄,民族等信息。在下方显示输入表单的值的动态变化。

const initialState = {
  name: "",
  age: 18,
  nationality: "汉族",
};
const reducer = (state, action) => {
  switch (action.type) {
    case "patch":
      return { ...state, ...action.formData };
    case "reset":
      return initialState;
    default:
      throw new Error("unknown type!");
  }
};
/* 在函数组件中使用useReducer */
const App = () => {
  const [formData, dispatch] = useReducer(reducer, initialState);
  const onSubmit = () => {
    alert("你点击了提交按钮");
  };
  const onReset = () => {
    dispatch({ type: "reset" });
  };
  return (
    <form onSubmit={onSubmit} onReset={onReset}>
      <div>
        <label>
          姓名
          <input
            type="text"
            value={formData.name}
            onChange={(e) => {
              dispatch({ type: "patch", formData: { name: e.target.value } });
            }}
          />
        </label>
      </div>
      <div>
        <label>
          年龄
          <input
            type="number"
            value={formData.age}
            onChange={(e) => {
              dispatch({ type: "patch", formData: { name: e.target.value } });
            }}
          />
        </label>
      </div>
      <div>
        <label>
          民族
          <input
            type="text"
            value={formData.nationality}
            onChange={(e) => {
              dispatch({
                type: "patch",
                formData: { nationality: e.target.value },
              });
            }}
          />
        </label>
      </div>
      <div>
        <button type="submit">提交</button>
        <button type="reset">重置</button>
      </div>
      <hr />
      {JSON.stringify(formData)}
    </form>
  );
};
ReactDOM.render(<App />, document.getElementById("root"));

在输入框中输入一些东西,下面会及时的更新显示出来的输入的数据:

示例4:使用 useReducer 代替 Redux

使用 createContext/useContext 模拟 Redux 的全局数据状态管理的作用域,useReducer 来表示全局数据状态管理中的所有读写操作。

在创建的上下文对象中能够及时更新数据,就类似于一个局部的 Redux。在以下代码中,案例:我将模拟一个 state 有三个变量 n,m,p。一个组件更新,其他组件的值也会连带着更新。

const store = {
  n: 0,
  m: 0,
  p: 0,
};
const reducer = (state, action) => {
  switch (action.type) {
    case "setN":
      return { ...state, n: state.n + action.number };
    case "setM":
      return { ...state, m: state.m + action.number };
    case "setP":
      return { ...state, p: state.p + action.number };
    default:
      throw new Error("unknown type!");
  }
};
/* 创建上下文对象--模拟一个Redux的作用域 */
const Context = React.createContext(null);
const App = () => {
  const [state, dispatch] = React.useReducer(reducer, store);
  return (
    <Context.Provider value={{ state, dispatch }}>
      <N />
      <M />
      <P />
    </Context.Provider>
  );
};
const N = () => {
  const { state, dispatch } = React.useContext(Context);
  const addClick = () => {
    dispatch({ type: "setN", number: 1 });
  };
  return (
    <div>
      <h1>N组件</h1>
      <div>n:{state.n}</div>
      <div>m:{state.m}</div>
      <div>p:{state.p}</div>
      <button onClick={addClick}>+1</button>
    </div>
  );
};
const M = () => {
  const { state, dispatch } = React.useContext(Context);
  const addClick = () => {
    dispatch({ type: "setM", number: 2 });
  };
  return (
    <div>
      <h1>M组件</h1>
      <div>n:{state.n}</div>
      <div>m:{state.m}</div>
      <div>p:{state.p}</div>
      <button onClick={addClick}>+2</button>
    </div>
  );
};
const P = () => {
  const { state, dispatch } = React.useContext(Context);
  const addClick = () => {
    dispatch({ type: "setP", number: 3 });
  };
  return (
    <div>
      <h1>P组件</h1>
      <div>n:{state.n}</div>
      <div>m:{state.m}</div>
      <div>p:{state.p}</div>
      <button onClick={addClick}>+3</button>
    </div>
  );
};
ReactDOM.render(<App />, document.getElementById("root"));

在一番对 n,m,p 数据的操作后,所有组件的值都变化了更新。

  

七、创建自己HOOKS:

上例的 Hooks 代码还可以封装起来,变成一个自定义的 Hook,便于共享。


const usePerson = (personId) => {
  const [loading, setLoading] = useState(true);
  const [person, setPerson] = useState({});
  useEffect(() => {
    setLoading(true);
    fetch(`https://swapi.co/api/people/${personId}/`)
      .then(response => response.json())
      .then(data => {
        setPerson(data);
        setLoading(false);
      });
  }, [personId]);  
  return [loading, person];
};

上面代码中,usePerson()就是一个自定义的 Hook。

Person 组件就改用这个新的钩子,引入封装的逻辑。

const Person = ({ personId }) => {
  const [loading, person] = usePerson(personId);

  if (loading === true) {
    return <p>Loading ...</p>;
  }

  return (
    <div>
      <p>You're viewing: {person.name}</p>
      <p>Height: {person.height}</p>
      <p>Mass: {person.mass}</p>
    </div>
  );
};

八、常见问题

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐