• (一)了解函数化组件 Hooks【useState,useEffect,useContext,useReducer】


    一. Hooks?


    Hook 是[React 16.8] 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

    Hook 是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数。Hook 不能在 class 组件中使用 —— 这使得你不使用 class 也能使用 React。


    二.为什么要使用 hooks?


    1. 难以理解的 class 、组件中必须去理解 javascript 与 this 的工作方式、需要绑定事件处理器、纯函数组件与 class 组件的差异存在分歧、甚至需要区分两种组件的使用场景;Hook 可以使你在非 class 的情况下可以使用更多的 React 特性

    2. 在组件之间复用状态逻辑很难、大型组件很难拆分和重构,也很难测试。Hook 使你在无需修改组件结构的情况下复用状态逻辑

    3. 复杂组件变得难以理解、业务逻辑分散在组件的各个方法之中,导致重复逻辑或关联逻辑。Hook 将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据)
    注意:hook和class组件是不能够同时使用的,否则就会出现报错


    三.hook 使用

    Hook 就是JavaScript 函数,但是使用它们会有两个额外的规则:
    1、只能在函数最外层调用 Hook。不要在循环、条件判断或者嵌套函数(子函数)中调用。
    2、只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。

    (一)基础Hook

    (1)state hook-useState 状态钩子


    对于使用过class组件,相信对于state肯定有很深的印象,对于一些需要用到的全局变量,**在class组件中我们常常采用的方式是this.state = {},但是在hook中我们采用的方式就是使用useState这个hook,然后就可以对这种全局变量进行引用,在引用时只需要用其变量名即可**,

    useState 状态钩子


    什么时候用 Hook? 如果你在编写函数组件并意识到需要向其添加一些 state,以前的做法是必须将其它转化为 class。现在你可以在现有的函数组件中使用 Hook

    1. import React, { useState } from 'react';
    2. function Example() {
    3.   // 声明一个叫 "count" 的 state 变量
    4.   const [count, setCount] = useState(0);
    5.   return (
    6.     <div>
    7.       <p>You clicked {count} times</p>
    8.       <button onClick={() => setCount(count + 1)}>Click me</button>
    9.     </div>
    10.   );
    11. }


    等价的 class 示例

    1. class Example extends React.Component {
    2.   constructor(props) {
    3.     super(props);
    4.     this.state = {
    5.       count: 0,
    6.     };
    7.   }
    8.   render() {
    9.     return (
    10.       <div>
    11.         <p>You clicked {this.state.count} times</p>
    12.         <button onClick={() => this.setState({ count: this.state.count + 1 })}>Click me</button>
    13.       </div>
    14.     );
    15.   }
    16. }


    在 useState()中,它接受状态的初始值作为参数,即上例中计数的初始值,它返回一个数组,其中数组第一项为一个变量,指向状态的当前值。类似 this.state,第二项是一个函数,用来更新状态,类似 setState
    上述例子中没有 class 继承、没有 this、没有生命周期、代码更加简洁、这就是使用 hooks 的意义;

    总结:
     

    1. 1. 引入useState hooK: import React, { useState } from 'react';
    2. 2. 声明一个叫 "count" 的 state 变量:
    3.   const [count, setCount] = useState(0);//useState(0),0是count的初始化值
    4. 3. 读取 State:  <p>You clicked {count} times</p>
    5. 4. 更新 State
    6. <button onClick={() => setCount(count + 1)}>
    7.     Click me
    8.   </button>

    声明多个 state 变量
    可以在一个组件中多次使用 State Hook:

    1. // 声明多个 state 变量
    2.   const [age, setAge] = useState(42);//声明age ,初始化值为42
    3.   const [fruit, setFruit] = useState('banana');//声明fruit,初始化值为banana
    4.   const [todos, setTodos] = useState([{ text: '学习 Hook' }]);声明todos,初始化值为{text: '学习 Hook'}


    (2)Effect Hook -useEffect 副作用钩子

    数据获取,设置订阅以及手动更改 React 组件中的 DOM 都属于副作用

    在 React 组件中有两种常见副作用操作:需要清除的和不需要清除的。

    1. 无需清除的 effect

    在 React 更新 DOM 之后运行一些额外的代码。比如发送网络请求,手动变更 DOM,记录日志,这些都是常见的无需清除的操作

     使用hook-useEffect副作用钩子,更新网页 title

    1. import React, { useState, useEffect } from 'react';
    2. function Example() {
    3. const [count, setCount] = useState(0);
    4. useEffect(() => {
    5. document.title = `You clicked ${count} times`;
    6. });
    7. return (
    8. <div>
    9. <p>You clicked {count} times</p>
    10. <button onClick={() => setCount(count + 1)}>
    11. Click me
    12. </button>
    13. </div>
    14. );
    15. }

    等价使用 class :没有使用hook的情况下 副作用(数据更新)操作放到 componentDidMount 和 componentDidUpdate 函数中

    1. class ClassTitle extends React.Component {
    2. constructor(props) {
    3. super(props)
    4. this.state = {
    5. count: 0,
    6. }
    7. }
    8. componentDidMount() {
    9. document.title = `You clicked ${this.state.count} times`
    10. }
    11. componentDidUpdate() {
    12. document.title = `You clicked ${this.state.count} times`
    13. }
    14. render() {
    15. return (
    16. <div>
    17. <h1>2. 没有使用hook的情况下 副作用(数据更新)操作</h1>
    18. <p>You clicked {this.state.count} times</p>
    19. <button onClick={() => this.setState({ count: this.state.count + 1 })}>
    20. Click me
    21. </button>
    22. </div>
    23. )
    24. }
    25. }

    总结:

    useEffect可以用来更好的处理副作用,如异步请求等;可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合

    useEffect(() => {}, [array]);

    useEffect()接受两个参数,第一个参数是你要进行的异步操作,第二个参数是一个数组,用来给出 Effect 的依赖项。只要这个数组发生变化,useEffect()就会执行。当第二项省略不填时,useEffect()会在每次组件渲染时执行。这一点类似于类组件的 componentDidMount

    实现一个 useEffect() 依赖项变化的例子

    1. import React, { useState, useEffect } from 'react';
    2. const AsyncCount = ({ countNum }) => {
    3. const [loading, setLoading] = useState(true);
    4. const [count, setCount] = useState(0);
    5. useEffect(() => {
    6. setLoading(true);
    7. setTimeout(() => {
    8. setLoading(false);
    9. setCount(countNum);
    10. }, 2000);
    11. }, [countNum]);
    12. return <>{loading ? <p>Loading...</p> : <p>{count}</p>}</>;
    13. };
    14. const TestCount = () => {
    15. const [count, setCount] = useState(0);
    16. const changeCount = (name) => {
    17. setCount(name);
    18. };
    19. return (
    20. <>
    21. <AsyncCount countNum={count} />
    22. <button
    23. onClick={() => {
    24. changeCount(count + 1);
    25. }}
    26. >
    27. 增加
    28. </button>
    29. <button
    30. onClick={() => {
    31. changeCount(count - 1);
    32. }}
    33. >
    34. 减少
    35. </button>
    36. </>
    37. );
    38. };
    39. export default TestCount;

    再上述例子中,我们把处理 count 异步的操作以及是否渲染 loading,都放在了 AsyncCount hook 中;把复杂操作,通过 hooks 提取出去;将组件中关联部分拆分; 

    那下面我们在做一个更加细化的拆分,拆出一个自己的 hook

    1. const useCount = (countNum) => {
    2. const [loading, setLoading] = useState(true)
    3. const [count, setCount] = useState(0)
    4. useEffect(() => {
    5. setLoading(true)
    6. setTimeout(() => {
    7. setLoading(false)
    8. setCount(countNum)
    9. }, 2000)
    10. }, [countNum])
    11. return [loading, count]
    12. }
    13. const AsyncCount = ({ countNum }) => {
    14. const [loading, count] = useCount(countNum)
    15. return <>{loading ? <p>Loading...</p> : <p>{count}</p>}</>
    16. }
    17. const TestCount = () => {
    18. const [count, setCount] = useState(0)
    19. const changeCount = (count) => {
    20. setCount(count)
    21. }
    22. return (
    23. <>
    24. <AsyncCount countNum={count} />
    25. <button
    26. onClick={() => {
    27. changeCount(count + 1)
    28. }}
    29. >
    30. 增加
    31. </button>
    32. <button
    33. onClick={() => {
    34. changeCount(count - 1)
    35. }}
    36. >
    37. 减少
    38. </button>
    39. </>
    40. )
    41. }

    上述 AsyncCount 组件中再次将它的副作用操作拆分;在此组件中只关注渲染结果,useCount 接受一个数字,返回一个数组,数组中包括状态,与 count 两个结果;在我们使用 useCount 时,会根据我们传入参数的不同而返回不同的状态; 

    2. 需要清除的Effect

    就是需要在componentWillUnmount中清除掉,例如我们使用setInterval来更新当前时间。

    1. class FriendStatus extends React.Component{
    2. constructor(props){
    3. super(props);
    4. this.state = { nowTime: null};
    5. this.timer = null;
    6. }
    7. componentDidMount(){
    8. this.timer = setInterval(() => {
    9. this.setState({
    10. nowTime: new Date()
    11. })
    12. }, 1000)
    13. }
    14. componentWillUnmount(){
    15. if (this.timer !== null) {
    16. clearInterval(timer);
    17. }
    18. }
    19. render(){
    20. let time = this.state.nowTime;
    21. return
    22. <div>{time.toString()}</div>
    23. }
    24. }

    使用hook,如下:

    1. import React, { useState, useEffect } from 'react';
    2. function FriendStatus(props) {
    3. const [nowTime, setNowTime] = useState(new Date());
    4. useEffect(() => {
    5. let timer = setInterval(() => {
    6. setNowTime(new Date())
    7. }, 1000)
    8. return () => { // 返回一个清理函数
    9. clearInterval(timer);
    10. };
    11. }, []);
    12. return(<div>{nowTime.toString()}</div>)
    13. }

    effect 可选的清除机制。每个 effect 都可以返回一个清除函数。如此可以将添加和移除订阅的逻辑放在一起。它们都属于 effect 的一部分。 

    3. 通过跳过Effect来进行性能优化

    每次渲染后都执行清理或者执行effect会导致性能问题。在class中我们在componentDidUpdate中添加prevProps和prevState的比较逻辑来解决;

    componentDidUpdate(prevProps, prevState){
        if(this.state.count !== prevState.count){
            document.title = `点击了{this.state.count}次`
        }
    }

    在hook中使用effect

     useEffect(() => {
        console.log('执行了--useEffect')
        document.title = `点击了{count}次`;
    }, [count]); // 在初次渲染和count发生变化时更新

    如果第二数组参数为[],则Effect会在初次渲染执行一次及包含清除函数Effect再执行一次(可将上述代码中的[count]替换为[]测试);下面分别给出两种情况的执行代码 

    1. // 带清除函数即为useEffect的第一个参数(函数)中再返回一个函数
    2. // 不带清除函数+第二个参数为[];
    3. // --> 整个生命周期只执行一次,相当于componentDidMount;
    4. useEffect(() => {
    5. console.log('执行了--useEffect');
    6. document.title = `点击了${count}次`;
    7. }, []);
    8. // 带清除函数+第二个参数为[];
    9. // --> 整个生命周期中执行了两次,相当于componentDidMount和componentWillUnmount;
    10. useEffect(() => {
    11. console.log('执行了--useEffect');
    12. document.title = `点击了${count}次`;
    13. return () => { // 相当于componentWillUnmount;
    14. console.log('执行了--清除函数');
    15. document.title = "";
    16. }
    17. }, [])

    如果不加第二个数组参数,则Effect除了会在初次渲染执行一次外,还会在每次更新都执行。

    (3)useContext() 共享状态钩子

    如果需要在深层次组件之间共享状态,可以使用 useContext()。context 提供了一种在组件之间共享 props 的方式,而不必显示地通过组件树的逐层传递 props; useContext 钩子比原始 class 组件中使用 context 更为方便

    接收一个由React.createContext()创建的context对象(此处定义为Mycontext),并返回这个context属性vaule绑定的值;通过useContext获取到最近的<Mycontext.Provider value="">的props传递的value值;如下用法所示

    // Mycontext为React.createContext的返回值
    const Mycontext = React.createContext();
    <Mycontext.provider value={}>
        ...
    </Mycontext.provider>
    --------------------------
    const value = useContext(Mycontext);//调用了useContext的组件总会在context值变化时重新渲染。

     dome 如下:

    1. import React, { useContext } from 'react';
    2. //1.接收一个 context 对象并返回该 context 的当前值
    3. const themes = {
    4. light: {
    5. foreground: '#000000',
    6. background: '#eeeeee',
    7. },
    8. dark: {
    9. foreground: '#ffffff',
    10. background: '#222222',
    11. },
    12. }
    13. // 当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定。
    14. const ThemeContext = React.createContext(themes.light)
    15. function Toolbar(props) {
    16. return (
    17. <div>
    18. <ThemedButton />
    19. </div>
    20. )
    21. }
    22. function ThemedButton() {
    23. //调用了useContext的组件总会在context值变化时重新渲染。
    24. const theme = useContext(ThemeContext)
    25. return (
    26. <button style={{ background: theme.background, color: theme.foreground }}>
    27. I am styled by theme context!
    28. </button>
    29. )
    30. }
    31. function ExampleHook() {
    32. return (
    33. <div>
    34. <ThemeContext.Provider value={themes.dark}>
    35. <Toolbar />
    36. </ThemeContext.Provider>
    37. </div>
    38. )
    39. }
    40. export default ExampleHook

    (二)额外的 Hook

    (1)useReducer

    在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。简单的说就是常常用于管理一些复杂的状态,适合 action 比较多的场景

    语法:

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

    参数:

    参数一:useReducer 接受一个reducer 函数,reducer 接受两个参数一个是 state 另一个是 action 。
    参数二:useReducer接受一个初始state, initialArg。将初始 state 作为第二个参数传入 useReducer 是最简单的方法。
    参数三:useReducer接受一个init函数,通过init(initialArg)来初始化 state 。这样可以惰性地创建初始 state。

    返回值:

    返回一个状态state和 dispath方法函数,state 是返回状态中的值,而 dispatch 是一个可以发布事件来更新 state 的。

    由此可知,有两种不同初始化useReducer 的state的方式:一种是直接在第二个参数传入初始state;另一种是在第三个参数通过init()创建初始state。 

    A 当两个参数时

    1. import React, { useReducer } from 'react';
    2. const init = {
    3. count: 0
    4. };
    5. function reducer(state, action){
    6. switch(action.type){
    7. case 'add':
    8. return {count: state.count + 1};
    9. case 'minus':
    10. return {count: state.count - 1};
    11. default: throw new Error();
    12. }
    13. }
    14. function TestReducer(){
    15. const [state, dispatch] = useReducer(reducer, init);
    16. return (
    17. <div>
    18. count: {state.count}
    19. <ul>
    20. <li><button onClick={() => dispatch({type: 'add'})}>+</button></li>
    21. <li><button onClick={() => dispatch({type: 'minus'})}>-</button></li>
    22. </ul>
    23. </div>
    24. )
    25. }
    26. export default TestReducer;

    三个参数

    1. import React ,{useReducer}from 'react';
    2. const App2 = () => {
    3. const initialState = { name:'张三' , location : '北京' , count : 0 }
    4. const init = (v) => {
    5. console.log('v2',Object.prototype.toString.call(v)==='[object Object]') //判断是否是对象
    6. console.log('v',v)
    7. return v
    8. }
    9. const reducer = ( state , action ) => {
    10. switch(action.type){
    11. case 'add':
    12. return {
    13. ...state,
    14. count : state.count + 1
    15. }
    16. case 'minus':
    17. return {
    18. ...state,
    19. count:state.count - 1
    20. }
    21. case 'reset':
    22. return init(action.payLoad)
    23. default :
    24. throw Error
    25. }
    26. }
    27. const [state, dispatch] = useReducer(reducer, initialState , init)
    28. return (
    29. console.log('state',state),
    30. <div>
    31. <div>
    32. <button onClick={()=>dispatch({type:'add'})}>加号</button>
    33. </div>
    34. 现在的值:{state.count}
    35. <div>
    36. <button onClick={()=>dispatch({type:'minus'})}>减号</button>
    37. </div>
    38. <div>
    39. <button onClick={()=>dispatch({type:'reset', payLoad : initialState})}>重置</button>
    40. </div>
    41. </div>
    42. );
    43. };
    44. export default App2;

    (2)useReducer + useContext 的组合

    在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state等。并且,使用 useReducer 还能给那些会触发深更新的组件做性能优化,因为你可以向子组件传递 dispatch 而不是回调函数

    大部分人并不喜欢在组件树的每一层手动传递回调。尽管这种写法更明确,但这给人感觉像错综复杂的管道工程一样麻烦。

    在大型的组件树中,我们推荐的替代方案是通过 context 用 useReducer 往下传一个 dispatch 函数

    1. import React, { useState, useEffect, useContext, useReducer } from 'react'
    2. // 1. 声明一个变量
    3. const init = {
    4. count: 1,
    5. }
    6. // 2. 创建需要共享的context
    7. const ThemeContext = React.createContext(null)
    8. function reducer(state, action) {
    9. switch (action.type) {
    10. case 'add':
    11. return { count: state.count + 1 }
    12. case 'minus':
    13. return { count: state.count - 1 }
    14. default:
    15. throw new Error()
    16. }
    17. }
    18. // Toolbar 组件并不需要透传 ThemeContext
    19. function Toolbar(props) {
    20. return (
    21. <div
    22. style={{
    23. backgroundColor: '#faad14',
    24. padding: '10px',
    25. }}
    26. >
    27. <p>这是子组件</p>
    28. <ThemedButton />
    29. </div>
    30. )
    31. }
    32. function ThemedButton(props) {
    33. // 5.使用共享 Context:如果我们想要执行一个 action,我们可以从 context 中获取 dispatch。
    34. const { state, dispatch } = useContext(ThemeContext)
    35. const handleClick = () => {
    36. dispatch({ type: 'add' })
    37. }
    38. return (
    39. <div
    40. style={{
    41. backgroundColor: '#f00',
    42. padding: '10px',
    43. }}
    44. >
    45. <p>这是孙组件:{state.count}</p>
    46. <button onClick={handleClick}>点击</button>
    47. </div>
    48. )
    49. }
    50. export default function HookreduceContext() {
    51. //4. 创建useReducer 对象
    52. const [state, dispatch] = useReducer(reducer, init)
    53. // 3.使用 Provider 提供 ThemeContext 的值,Provider所包含的子树都可以直接访问ThemeContext的值
    54. return (
    55. <div
    56. style={{
    57. backgroundColor: '#13ce66',
    58. padding: '10px',
    59. width: '200px',
    60. margin: 'auto',
    61. marginTop: '20px',
    62. }}
    63. >
    64. <ThemeContext.Provider value={{ state, dispatch }}>
    65. <p>这是父组件</p>
    66. <Toolbar />
    67. </ThemeContext.Provider>
    68. </div>
    69. )
    70. }

    useReducer + useContext 的组合,能够在子组件里面能够获取到上级组件传递过来的状态,并且能够进行修改

     总结:

    1.useContext 创建全局状态,不用一层一层的传递状态。
    2. useReducer 创建 reducer,并根据不同的 dispatch 更新 state。
    3.代码写到哪里状态就加到哪里,不用打断思路跳到 redux 里面去写。
    4. 全局状态分离,避免项目变大导致 Redux 状态树难以管理。

  • 相关阅读:
    运维开发实践 - Docker - 容器实现原理
    微信小程序控制元素显示隐藏
    .gitignore文件忽略的内容不生效问题解决
    网络报修心得
    Python多任务编程
    Tomcat 8(一)Tomcat常用配置
    学python的第十八天
    共享店铺系统如何帮助门店提升业绩?
    ASO优化含义篇:积分墙是什么?
    C#.NET Framework RSA 私钥签名 公钥验签(验证签名) ver:20230612
  • 原文地址:https://blog.csdn.net/gao_xu_520/article/details/125386551