以前,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) 。如果函数内部直接包含产生副效应的操作,就不再是纯函数了,我们称之为不纯的函数。
纯函数内部只有通过间接的手段(即通过其他函数调用),才能包含副效应。
说了半天,那么钩子到底是什么?
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 函数中调用。
用于为函数组件引入状态(state)。纯函数不能有状态,所以把状态放在钩子里面。
React.useState(params) 设置第一个参数的初始值 。
state是解构出的第一个参数 。
setCount 是第二个参数,是一个函数用来更新state,并且会触发新的渲染。同时,在后续新的渲染中 React.useState() 返回的第一个 state 值始终是最新的。
- import React, { useState } from 'react'
-
- // const [state, setState] = React.useState(initialState)
-
- const [stateN, setStateN] = React.useState(0)
可以这么操作 setCount(stateN + 1),这样 stateN设置称为最新的值。
setXxx()2种写法:
setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值
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("页面刷新了")
- const [count,setCount] = useState(0)
- const add = ()=>{
- setCount(count+1)
- }
- return (
- <div>
- <div>{count}div>
- <button onClick={add}>+1button>
- div>
- )
- }
-
- ReactDom.render(<App/>,document.querySelector("#root"))
用来引入具有副作用的操作,就是指定一个副效应函数,组件每渲染一次,该函数就自动执行一次。组件首次在网页 DOM 加载后,副效应函数也会执行。(可用于模拟类组件中的生命周期钩子),最常见的就是向服务器请求数据。以前,放在componentDidMount里面的代码,现在可以放在useEffect()。
- useEffect(() => {
- // Async Action
- }, [dependencies])
它接受2个参数,第一个是函数,第二个是数组;
如果第二个参数数组为空,它就是componentDidMount,只有第一次渲染;
如果第二个参数数组里面有值,它就是componentDidUpdate,只要值改变了,它就渲染;
- const Person = ({ 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])
-
- 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>
- }
上面代码中,每当组件参数personId发生变化,useEffect()就会执行。组件第一次渲染时,useEffect()也会执行。
- function Welcome(props) {
- useEffect(() => {
- document.title = `Hello, ${props.name}`;
- }, [props.name]);
- return <h1>Hello, {props.name}h1>;
- }
上面例子中,useEffect()的第二个参数是一个数组,指定了第一个参数(副效应函数)的依赖项(props.name)。只有该变量发生变化时,副效应函数才会执行。
如果第二个参数是一个空数组,就表明副效应参数没有任何依赖项。因此,副效应函数这时只会在组件加载进入 DOM 后执行一次,后面组件重新渲染,就不会再次执行。这很合理,由于副效应不依赖任何变量,所以那些变量无论怎么变,副效应函数的执行结果都不会改变,所以运行一次就够了。
只要是副效应,都可以使用useEffect()引入。它的常见用途有下面几种。
- 获取数据(data fetching)
- 事件监听或订阅(setting up a subscription)
- 改变 DOM(changing the DOM)
- 输出日志(logging)
- 下面是从远程服务器获取数据的例子。
- 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(() => {
- const subscription = props.source.subscribe();
- return () => {
- subscription.unsubscribe();
- };
- }, [props.source]);
上面例子中,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])
写在函数式组件里的 “函数调用代码”。如果函数式组件重新渲染时,每次都会执行“调用函数的代码”。如果不是必须的,那么就是性能的浪费。useMemo就是解决这个问题的。即:useMemo是防止不必要的的函数调用。
useMemo 解决的是 防止函数式组件里的 “调用函数的代码” 被多次被执行。既就是:useMemo 是保证函数式组件重新渲染时,组件里的“函数调用代码” 的执行时可控的。
useMemo(函数, 数组); //当数组中的其中一个元素,发生变化时,就会调用 函数 。
- 如: const nameStr = useMemo(()=>genName(name),[name])
-
- 表示,当name发生变化时,才会调用 ()=>genName(name)函数
接受两个参数,第一个参数为()=>value;
第二个参数是依赖,为[m,n];
只有当依赖变化时,才会计算新的value;
如果依赖不变,那么就会重用之前的value;
就像Vue2中的计算属性computed一样。
以下代码中,如果不使用useMemo,当我们点击“修改name”的按钮时,也调用了函数isAdult()。这其实是性能的损耗。如果加上了useMemo。那么,只有点击“修改age” 按钮时,才会调用isAdult(),因为,我的依赖写的是age。
- //父组件:
- import { useState } from "react";
- import SonFn from "./SonFn";
-
- export default () => {
- console.log("父组件");
-
- const [name, setName] = useState('张三疯')
- const [age, setAge] = useState(12)
-
- return (
- <>
- <h1>useMemoh1>
- <input type="button"
- value="修改name"
- onClick={()=>setName(name+"1")} />
- <input type="button"
- value="修改age"
- onClick={()=>setAge(age+1)} />
- <hr/>
- <SonFn name={name} age={age} />
- >
- )
- }
-
- //子组件:
-
-
- 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(()=>{
- 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。
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("没事执行玩玩", count + "$");
- };
- return (
-
- <h2>HookTest:useCallback----useMemoh2>
- <h3>useCallBackh3>
- <h4>
- count:{count}---num:{num}
- h4>
- <button onClick={showCount}>showCountbutton>
- <button onClick={(e) => setcount(count + 3)}>+3button>
- <button onClick={(e) => setnum(num * 10)}>*10button>
- );
- });
-
- export default HookTest;
正常执行的话,修改count 和 num 都会触发 render ,这是毫无疑问的。那么showCount 每次render也会被重新创建一次,这也是情理之中的。 但是我们想想,真的有必要每次render都重新创建showCount 吗?现在可能只有一个函数,函数体也简单,重新创建也影响不了什么,但是真正开发的时候却是我们要考虑的问题。 所以 useCallback 来帮我们解决这个问题了!
- const showCount = () => {
- console.log("没事执行玩玩", count + "$");
- };
-
- const showCount = useCallback(() => {
- console.log("没事执行玩玩", 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 inputbutton>
- >
- );
- }
本质上,useRef 就像是可以在其 .current 属性中保存一个可变值的“盒子”。
你应该熟悉 ref 这一种访问 DOM 的主要方式。如果你将 ref 对象以
形式传入组件,则无论该节点如何改变,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}>+1button>
- 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' })}>incrementbutton>
- <button onClick={() => dispath({ type: 'decrement' })}>decrementbutton>
- <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}>+1button>
- <button onClick={onclick2}>+2button>
- <button onClick={onclick3}>x2button>
- div>
- );
- };
- ReactDOM.render(<App />, document.getElementById("root"));
用法与 useState 类似,从 useReducer 中得到读接口 state,写接口 dispatch。最后操作时传参给 dispatch 写接口。操作灵活多变,比 useState 好处就是能聚集所有的操作和各种状态量。着重理解这几行代码,读懂。
useReducer 算是 useState 的复杂版。
使用 useReducer 分以下步骤:
- 创建初始值的状态initialState
- 创建所有对状态的操作reducer(state,action)
- 传给useReducer,得到读和写的接口
- 调用写({'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}>+1button>
- 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}>+2button>
- 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}>+3button>
- 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>
- );
- };
八、常见问题