(1) Understand functional component Hooks [useState, useEffect, useContext, useReducer]

1. Hooks?


Hooks are a new feature in [React 16.8]  . It allows you to use state and other React features without writing classes.

Hooks are functions that allow you to "hook in" features such as React state and lifecycle in function components. Hooks cannot be used in class components - this allows you to use React without using classes.


2. Why use hooks?


1. Incomprehensible classes, components must understand how javascript and this work, event handlers need to be bound, there are differences between pure function components and class components, and even the usage scenarios of the two components need to be distinguished; Hook can be used You can use more React features without classes .

2. It is difficult to reuse state logic between components, it is difficult to split and reconstruct large components, and it is also difficult to test. Hooks allow you to reuse state logic without modifying the component structure .

3. Complex components become difficult to understand, and business logic is scattered among various methods of the component, resulting in duplicate logic or associated logic. Hook splits the interrelated parts of the component into smaller functions (such as setting subscriptions or requesting data).
Note: Hook and class components cannot be used at the same time , otherwise an error will occur.


3. Hook usage

Hooks are JavaScript functions, but there are two additional rules for using them:
1. Hooks can only be called at the outermost level of the function. Do not call it in a loop, conditional judgment or nested function (subfunction).
2. Hook can only be called in React function components. Do not call within other JavaScript functions.

(1) Basic Hook

(1)state hook- useState state hook


For those who have used class components, I believe they must have a deep impression of state. For some global variables that need to be used, **In class components, the method we often use is this.state = {}, but in hooks we use The best way is to use the useState hook, and then you can reference this global variable. When referencing, you only need to use its variable name**,

useState state hook


When to use Hook? If you're writing a functional component and realize you need to add some state to it, the old way of doing it was that you had to convert the rest to a class. Now you can use Hooks in existing function components .

import React, { useState } from 'react';

function Example() {
  // 声明一个叫 "count" 的 state 变量
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}


Equivalent class example

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0,
    };
  }

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>Click me</button>
      </div>
    );
  }
}


In useState(), it accepts the initial value of the state as a parameter, which is the initial value of the count in the above example. It returns an array, where the first item of the array is a variable pointing to the current value of the state. Similar to this.state, the second item is a function used to update the state, similar to setState .
In the above example, there is no class inheritance, no this, no life cycle, and the code is more concise. This is the meaning of using hooks;

Summarize:
 

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

Declare multiple state variables.
State Hook can be used multiple times in a component:

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


(2)Effect Hook -useEffect side effect hook

Fetching data, setting subscriptions, and manually changing the DOM in React components are all side effects .

There are two common side effects in React components: those that need to be cleared and those that don't.

1. Effects that do not require clearing

Run some additional code after React updates the DOM. For example, sending network requests, manually changing the DOM, and recording logs are all common operations that do not require clearing .

 Use the hook-useEffect side effect hook to update the webpage title

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

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

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

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

Equivalent to using class: without using hook, the side effect (data update) operation is placed in the componentDidMount and componentDidUpdate functions.

class ClassTitle extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      count: 0,
    }
  }

  componentDidMount() {
    document.title = `You clicked ${this.state.count} times`
  }
  componentDidUpdate() {
    document.title = `You clicked ${this.state.count} times`
  }

  render() {
    return (
      <div>
        <h1>2. 没有使用hook的情况下 副作用(数据更新)操作</h1>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    )
  }
}

Summarize:

useEffect can be used to better handle side effects, such as asynchronous requests, etc.; useEffect Hook can be regarded as a combination of componentDidMount, componentDidUpdate and componentWillUnmount .

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

useEffect() accepts two parameters. The first parameter is the asynchronous operation you want to perform, and the second parameter is an array used to give the dependencies of Effect. As long as this array changes, useEffect() will be executed. When the second item is omitted, useEffect() will be executed every time the component is rendered. This is similar to componentDidMount for class components .

Example of implementing a useEffect() dependency change

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

const AsyncCount = ({ countNum }) => {
  const [loading, setLoading] = useState(true);
  const [count, setCount] = useState(0);

  useEffect(() => {
    setLoading(true);
    setTimeout(() => {
      setLoading(false);
      setCount(countNum);
    }, 2000);
  }, [countNum]);
  return <>{loading ? <p>Loading...</p> : <p>{count}</p>}</>;
};

const TestCount = () => {
  const [count, setCount] = useState(0);
  const changeCount = (name) => {
    setCount(name);
  };
  return (
    <>
      <AsyncCount countNum={count} />
      <button
        onClick={() => {
          changeCount(count + 1);
        }}
      >
        增加
      </button>
      <button
        onClick={() => {
          changeCount(count - 1);
        }}
      >
        减少
      </button>
    </>
  );
};

export default TestCount;

In the above example, we put the asynchronous operation of processing count and whether to render loading in the AsyncCount hook; extract the complex operations through hooks; split the related parts of the component; 

Next, we are doing a more detailed split and detaching a hook of our own.

const useCount = (countNum) => {
  const [loading, setLoading] = useState(true)
  const [count, setCount] = useState(0)

  useEffect(() => {
    setLoading(true)
    setTimeout(() => {
      setLoading(false)
      setCount(countNum)
    }, 2000)
  }, [countNum])
  return [loading, count]
}

const AsyncCount = ({ countNum }) => {
  const [loading, count] = useCount(countNum)
  return <>{loading ? <p>Loading...</p> : <p>{count}</p>}</>
}

const TestCount = () => {
  const [count, setCount] = useState(0)
  const changeCount = (count) => {
    setCount(count)
  }
  return (
    <>
      <AsyncCount countNum={count} />
      <button
        onClick={() => {
          changeCount(count + 1)
        }}
      >
        增加
      </button>
      <button
        onClick={() => {
          changeCount(count - 1)
        }}
      >
        减少
      </button>
    </>
  )
}

The above AsyncCount component once again splits its side effect operations; in this component, we only focus on the rendering results. useCount accepts a number and returns an array. The array includes status and count. When we use useCount, it will be based on We return different states depending on the parameters we pass in; 

2. Effects that need to be cleared

It just needs to be cleared in componentWillUnmount. For example, we use setInterval to update the current time.

class FriendStatus extends React.Component{
    constructor(props){
        super(props);
        this.state = { nowTime: null};
        this.timer = null;
    }
    componentDidMount(){
        this.timer = setInterval(() => {
            this.setState({
                nowTime: new Date()
            })
        }, 1000)
    } 
    componentWillUnmount(){
        if (this.timer !== null) {
            clearInterval(timer);
        }
    }
    render(){
        let time = this.state.nowTime;
        return(
           <div>{time.toString()}</div>
        )
    }
}

Use hook as follows:

import React, { useState, useEffect } from 'react';
 
function FriendStatus(props) {
  const [nowTime, setNowTime] = useState(new Date());
 
  useEffect(() => {    
    let timer = setInterval(() => {
        setNowTime(new Date())
    }, 1000)
    return () => {   // 返回一个清理函数
      clearInterval(timer);
    };
  }, []);
  return(<div>{nowTime.toString()}</div>)
}

effect optional cleanup mechanism. Each effect can return a cleanup function. This puts the logic for adding and removing subscriptions together. They are all part of the effect

3. Performance optimization by skipping Effect

Performing cleanup or executing effects after each rendering will cause performance problems. In the class, we add the comparison logic between prevProps and prevState in componentDidUpdate to solve the problem;

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

Using effects in hooks

 useEffect(() => {     console.log('--useEffect' was executed)     document.title = `Clicked {count} times`; }, [count]); // Updated when initial rendering and count change


If the second array parameter is [], Effect will be executed once during the initial rendering and once again including the clearing function Effect (you can replace [count] in the above code with [] for testing); the two situations are given below. Execute code 

// 带清除函数即为useEffect的第一个参数(函数)中再返回一个函数
// 不带清除函数+第二个参数为[];
// --> 整个生命周期只执行一次,相当于componentDidMount;
useEffect(() => {
    console.log('执行了--useEffect');
    document.title = `点击了${count}次`;
}, []);
 
// 带清除函数+第二个参数为[];
// --> 整个生命周期中执行了两次,相当于componentDidMount和componentWillUnmount;
useEffect(() => {
    console.log('执行了--useEffect');
    document.title = `点击了${count}次`; 
    return () => {  // 相当于componentWillUnmount;
        console.log('执行了--清除函数');
        document.title = "";   
    }
}, [])

If you do not add the second array parameter, Effect will be executed not only once for the initial rendering, but also for each update.

(3) useContext() shared state hook

If you need to share state between deep components, you can use useContext(). Context provides a way to share props between components without having to explicitly pass props through the component tree layer by layer ; the useContext hook is more convenient than using context in the original class component ;

Receives a context object created by React.createContext() (defined here as Mycontext), and returns the value bound to the context attribute vaule; obtain the props passed by the nearest <Mycontext.Provider value=""> through useContext value value; as shown in the following usage

// Mycontext is the return value of React.createContext
const Mycontext = React.createContext();
<Mycontext.provider value={}>
    ...
</Mycontext.provider>
------------- -------------
const value = useContext(Mycontext);//The component that calls useContext will always re-render when the context value changes.

 dome is as follows:


import React, { useContext } from 'react';

//1.接收一个 context 对象并返回该 context 的当前值
const themes = {
    light: {
      foreground: '#000000',
      background: '#eeeeee',
    },
    dark: {
      foreground: '#ffffff',
      background: '#222222',
    },
  }
// 当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定。
const ThemeContext = React.createContext(themes.light)
function Toolbar(props) {
    return (
      <div>
        <ThemedButton />
      </div>
    )
  }
  
  function ThemedButton() {
    //调用了useContext的组件总会在context值变化时重新渲染。
    const theme = useContext(ThemeContext)
    return (
      <button style={
   
   { background: theme.background, color: theme.foreground }}>
        I am styled by theme context!
      </button>
    )
  }
function ExampleHook() {
  return (
    <div>
      <ThemeContext.Provider value={themes.dark}>
        <Toolbar />
      </ThemeContext.Provider>
    </div>
  )
}
export default ExampleHook

(2) Additional Hooks

(1)useReducer

In some scenarios, useReducer it is  useState more applicable, for example, the state logic is complex and contains multiple sub-values, or the next state depends on the previous state, etc. To put it simply, it is often used to manage some complex states and is suitable for scenarios with a lot of actions .

grammar:

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

parameter:

Parameter 1: useReducer accepts a reducer function, and reducer accepts two parameters, one is state and the other is action.
Parameter two: useReducer accepts an initial state, initialArg. Passing the initial state as the second parameter to useReducer is the simplest method.
Parameter three: useReducer accepts an init function and initializes state through init (initialArg). This creates the initial state lazily.

return value:

返回A state stateand  dispathmethod function, state returns the value in the state, and dispatch is a function that can publish events to update the state.

It can be seen that there are 两种different ways 初始化to useReducer state: one is to directly pass in the initial state in the second parameter; the other is to create the initial state through init() in the third parameter

A两个参数时

import React, { useReducer } from 'react';
const init = {
 count: 0
};
 
function reducer(state, action){
  switch(action.type){
    case 'add': 
     return {count: state.count + 1};
    case 'minus':
      return {count: state.count - 1};
    default: throw new Error();
  }
}
 
function TestReducer(){
  const [state, dispatch] = useReducer(reducer, init);
  return (
    <div>
      count: {state.count}
      <ul>
        <li><button onClick={() => dispatch({type: 'add'})}>+</button></li>
        <li><button onClick={() => dispatch({type: 'minus'})}>-</button></li>
      </ul>
    </div>
  )
}
 
export default TestReducer;

三个参数

import React ,{useReducer}from 'react';

const App2 = () => {
    const initialState = { name:'张三' , location : '北京' , count : 0 }
    const init = (v) => {
        console.log('v2',Object.prototype.toString.call(v)==='[object Object]') //判断是否是对象
        console.log('v',v)
        return v
    }
    const reducer = ( state , action ) => {

        switch(action.type){
            case 'add':
                return {
                    ...state,
                    count : state.count + 1
                }
            case 'minus':
                return {
                    ...state,
                    count:state.count - 1
                }
                
            case 'reset':
                return init(action.payLoad)
            default : 
                throw Error
        }
        
        
    }
    const [state, dispatch] = useReducer(reducer, initialState , init)
    return (
        console.log('state',state),
        <div>
            <div>
                <button onClick={()=>dispatch({type:'add'})}>加号</button>
            </div>
                现在的值:{state.count}
            <div>
                <button onClick={()=>dispatch({type:'minus'})}>减号</button>
            </div>
            <div>
                <button onClick={()=>dispatch({type:'reset', payLoad : initialState})}>重置</button>
            </div>
        </div>
    );
};

export default App2;

(2)useReducer + useContext 的组合

In some scenarios, useReducer is more suitable than useState . For example, the state logic is complex and contains multiple sub-values , or the next state depends on the previous state, etc. Moreover, using useReducer can also optimize the performance of components that trigger deep updates, because you can pass dispatch to child components instead of callback functions .

Most people don't like manually passing callbacks through each level of the component tree. Although this way of writing is more explicit, it feels like an intricate piece of plumbing work.

In large component trees, our recommended alternative is to use useReducer to pass a dispatch function down through the context.

import React, { useState, useEffect, useContext, useReducer } from 'react'
// 1. 声明一个变量
const init = {
  count: 1,
}
// 2. 创建需要共享的context
const ThemeContext = React.createContext(null)

function reducer(state, action) {
  switch (action.type) {
    case 'add':
      return { count: state.count + 1 }
    case 'minus':
      return { count: state.count - 1 }
    default:
      throw new Error()
  }
}

// Toolbar 组件并不需要透传 ThemeContext
function Toolbar(props) {
  return (
    <div
      style={
   
   {
        backgroundColor: '#faad14',
        padding: '10px',
      }}
    >
      <p>这是子组件</p>
      <ThemedButton />
    </div>
  )
}

function ThemedButton(props) {
  // 5.使用共享 Context:如果我们想要执行一个 action,我们可以从 context 中获取 dispatch。
  const { state, dispatch } = useContext(ThemeContext)
  const handleClick = () => {
    dispatch({ type: 'add' })
  }
  return (
    <div
      style={
   
   {
        backgroundColor: '#f00',
        padding: '10px',
      }}
    >
      <p>这是孙组件:{state.count}</p>
      <button onClick={handleClick}>点击</button>
    </div>
  )
}

export default function HookreduceContext() {
  //4. 创建useReducer 对象
  const [state, dispatch] = useReducer(reducer, init)
  // 3.使用 Provider 提供 ThemeContext 的值,Provider所包含的子树都可以直接访问ThemeContext的值
  return (
    <div
      style={
   
   {
        backgroundColor: '#13ce66',
        padding: '10px',
        width: '200px',
        margin: 'auto',
        marginTop: '20px',
      }}
    >
      <ThemeContext.Provider value={
   
   { state, dispatch }}>
        <p>这是父组件</p>
        <Toolbar />
      </ThemeContext.Provider>
    </div>
  )
}

useReducer + useContext 的组合, able to be 子组件able to 获取到上级组件传递过来的状态, and be able to进行修改

 Summarize:

1.useContext creates global state without passing state layer by layer.
2. useReducer creates a reducer and updates state according to different dispatches.
3. Add the status wherever the code is written, without interrupting the train of thought and jumping to redux to write it.
4. Separate the global state to avoid the project becoming larger and making the Redux state tree difficult to manage.

Guess you like

Origin blog.csdn.net/gao_xu_520/article/details/125386551
Recommended