React-props (1) React-props (2) React-props (2)

We introduced some of the commonly used hooks in our second article, so let’s move on to the rest

useReducer

As an alternative to useState. It receives a Reducer of the form (state, action) => newState and returns the current state and its accompanying dispatch method. (If you’re familiar with Redux, you already know how it works.)

For those of you who don’t understand the Redux workflow, check out the Redux series of analysis Middleware principles.

Why use

Official: useReducer is more useful than useState in some situations, such as when the state logic is complex and contains multiple subvalues, or when the next state depends on the previous state. Also, using useReducer can optimize performance for components that trigger deep updates because you can pass dispatches to child components instead of callbacks.

To sum up:

  • If your state is a complex data structure like an array or an object

  • If your state changes are complex, often many states need to be changed in one operation

  • If you want to build automated test cases to ensure the stability of the application

  • If you need to modify some state in the deeper subcomponent (i.e. UseReducer +useContext instead of Redux)

  • If you have a large application, you want the UI and business to be maintained separately

Login scenarios

For example 🌰 :

Login scenarios

UseState Completes the login scenario

    function LoginPage({

        const [name, setName] = useState(' '); / / user name

        const [pwd, setPwd] = useState(' '); / / password

        const [isLoading, setIsLoading] = useState(false); // Whether to display loading, sending a request

        const [error, setError] = useState(' '); // Error message

        const [isLoggedIn, setIsLoggedIn] = useState(false); // Whether to log in



        const login = (event) = > {

            event.preventDefault();

            setError(' ');

            setIsLoading(true);

            login({ name, pwd })

                .then((a)= > {

                    setIsLoggedIn(true);

                    setIsLoading(false);

                })

                .catch((error) = > {

                    // Login failure: Error information is displayed, the user name and password are cleared, and the loading flag is cleared

                    setError(error.message);

                    setName(' ');

                    setPwd(' ');

                    setIsLoading(false);

                });

        }

        return ( 

            // Return to page JSX Element

        )

    }

Copy the code

UseReducer Completes the login scenario

    const initState = {

        name' '.

        pwd' '.

        isLoadingfalse.

        error' '.

        isLoggedInfalse.

    }

    function loginReducer(state, action{

        switch(action.type) {

            case 'login':

                return {

. state,

                    isLoadingtrue.

                    error' '.

                }

            case 'success':

                return {

. state,

                    isLoggedIntrue.

                    isLoadingfalse.

                }

            case 'error':

                return {

. state,

                    error: action.payload.error,

                    name' '.

                    pwd' '.

                    isLoadingfalse.

                }

            default

                return state;

        }

    }

    function LoginPage({

        const [state, dispatch] = useReducer(loginReducer, initState);

        const { name, pwd, isLoading, error, isLoggedIn } = state;

        const login = (event) = > {

            event.preventDefault();

            dispatch({ type'login' });

            login({ name, pwd })

                .then((a)= > {

                    dispatch({ type'success' });

                })

                .catch((error) = > {

                    dispatch({

                        type'error'

                        payload: { error: error.message }

                    });

                });

        }

        return ( 

            // Return to page JSX Element

        )

    }

Copy the code

❗️ Our state changes are very complex, and often many states need to be modified in one operation. Another advantage is that all state processing is centralized, which makes us have more control over state changes and it is easier to reuse state logic change codes. For example, in other functions to trigger the login success state, just dispatch({type: ‘success’}).

UseReducer should not be used to replace useState for the time being. After all, the writing method of Redux is very complicated

Complex data structure scenarios

Just recently, the author encountered complex data structure scenarios in his project. However, he did not use useReducer to solve the problem, but still used useState. The reason is simple: convenience

// Define the list type

  export interface IDictList extends IList {

  extChild: {

    curPage: number

    totalSize: number

    size: number // pageSize

    list: IList[]

   }

 }

 const [list, setList] = useState<IDictList[]>([])



 const change=(a)= >{

   const datalist = JSON.parse(JSON.stringify(list)) // Copy object address is different, but this writing method is not good, I suggest using reducers should encapsulate the following reducers writing method

   const data = await getData()

      const { totalCount, pageSize, list } = data

      item.extChild.totalSize = totalCount

      item.extChild.size = pageSize

      item.extChild.list = list

      setList(datalist) / / change

 }

Copy the code

As typescript type declarations show, the list variable is a complex data structure that often changes the contents of the extChild-list Array, but array.prototype. push does not trigger updates. Parse (json.stringify (list)) const datalist = json.parse (list)) Although I don’t use useReducer as an alternative, I recommend you try it

How to use

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

Copy the code

Knowledge collection

Refer to the same

UseReducer returns the same state as ref. The reference is constant and does not change as the function component is updated. Therefore, useReducer can also solve closure traps

const setCountReducer = (state,action) = >{

  switch(action.type){

    case 'add':

      return state+action.value

    case 'minus':

      return state-action.value

    default:

      return state

  }

}



const App = (a)= >{

  const [count,dispatch] = useReducer(setCountReducer,0)

  useEffect((a)= >{

    const timeId = setInterval((a)= >{

      dispatch({type:'add'.value:1})

    },1000)

    return (a)= > clearInterval(timeId)

}, [])

  return (

    <span>{count}</span>

  )

}

Copy the code

Change setCount to useReducer’s dispatch, because the identity of useReducer’s dispatch is always stable — even if the Reducer function is defined inside the component and depends on props

useContext

UseContext, which must be related to react. createContext, receives a context object (the return value of react. createContext) and returns the current value of that context. The current context value is determined by the < MyContext.provider > value prop of the upper-layer component closest to the current component.

Why use

If you’re already familiar with the Context API before you hit the Hook, it should make sense, UseContext (MyContext) is equivalent to static contextType = MyContext or < myContext.consumer > in the class component. In short, useContext is used to consume the context API

How to use

const value = useContext(MyContext);

Copy the code

Knowledge collection

UseContext causes react. memo to be invalid

When the component’s upper layer is updated recently, the Hook triggers a rerender and uses the latest Context value passed to the MyContext Provider. Even if the ancestor uses React. Memo or shouldComponentUpdate, ❗️ will be rerendered when the component itself uses useContext.

For example 🌰 :

// Create a context

const Context = React.createContext()

// Parcel with memo

const Item = React.memo((props) = > {

  // Component 1, useContext

  const count = useContext(Context);

  console.log('props', props)

  return (

    <div>{count}</div>

  )

})



const App = (a)= > {

  const [count, setCount] = useState(0)

  return (

    <div>

Number of clicks: {count}

      <button onClick={()= >{setCount(count + 1)}}> click me</button>

      <Context.Provider value={count}>

        <Item />

      </Context.Provider>

    </div>

  )

}

Copy the code

Results:

As you can see, the Memo becomes invalid when the last < myContext. Provider> update on the upper layer of the component triggers a rerender, even though the props are unchanged

Alternative Redux Hooks

With useReducer and useContext and the React. CreateContext API, we can implement our own state management to replace Redux

Implement the react – story

react-redux:React Redux is the official React binding for Redux. It lets your React components read data from a Redux store, and dispatch actions to the store to update data.

React and Redux connect components to the data center. Read the official document or read ruan Yifeng’s article. Here we will implement the two main apis

The Provider component

Provider: The data shared between components is passed by the top component of the Provider through props. Store must be placed in the Provider component as a parameter

It’s easy to implement using the React.createContext API, which react-Redux itself relies on

const MyContext = React.createContext()



const MyProvider = MyContext.Provider



export default MyProvider / / export

Copy the code

connect

Connect: Connect is a high-level component that provides a connection function that can be used to connect components to a Store. It provides the ability for a component to get data from a store or update its interfaces (mapStateToProps and mapStateToProps)

connect([mapStateToProps], [mapStateToProps], [mergeProps], [options])

function connect(mapStateToProps, mapDispatchToProps{

    return function (Component{

        return function ({

            const {state, dispatch} = useContext(MyContext)

            const stateToProps = mapStateToProps(state)

            const dispatchToProps = mapDispatchToProps(dispatch)

            constprops = {... props, ... stateToProps, ... dispatchToProps}

            return (

                <Component {. props} / >

            )

        }

    }

}



Export default connect // Export

Copy the code

Create the store

Store: The store object contains all data. If you want data at a point in time, take a snapshot of the Store. This collection of data at this point in time is called State.

Use useReducer to create our store

 import React, { Component, useReducer, useContext } from 'react';

import { render } from 'react-dom';

import './style.css';



const MyContext = React.createContext()

const MyProvider = MyContext.Provider;



function connect(mapStateToProps, mapDispatchToProps{

    return function (Component{

        return function ({

            const {state, dispatch} = useContext(MyContext)

            const stateToProps = mapStateToProps(state)

            const dispatchToProps = mapDispatchToProps(dispatch)

            constprops = {... props, ... stateToProps, ... dispatchToProps}



            return (

                <Component {. props} / >

            )

        }

    }

}



function FirstC(props) {

The console. The log (" FirstC update ")

    return (

        <div>

             <h2>This is FirstC</h2>

            <h3>{props.books}</h3>

            <button onClick={()= > props.dispatchAddBook("Dan Brown: Origin")}>Dispatch 'Origin'</button>

        </div>

    )

}



function mapStateToProps(state) {

    return {

        books: state.Books

    }

}



function mapDispatchToProps(dispatch) {

    return {

        dispatchAddBook: (payload)=> dispatch({type: 'ADD_BOOK', payload})

    }

}



const HFirstC = connect(mapStateToProps, mapDispatchToProps)(FirstC)



function SecondC(props) {

The console. The log (" SecondC update ")

    return (

        <div>

            <h2>This is SecondC</h2>

            <h3>{props.books}</h3>

            <button onClick={()= > props.dispatchAddBook("Dan Brown: The Lost Symbol")}>Dispatch 'The Lost Symbol'</button>

        </div>

    )

}



function _mapStateToProps(state) {

    return {

        books: state.Books

    }

}



function _mapDispatchToProps(dispatch) {

    return {

        dispatchAddBook: (payload)=> dispatch({type: 'ADD_BOOK', payload})

    }

}



const HSecondC = connect(_mapStateToProps, _mapDispatchToProps)(SecondC)





function App () {

    const initialState = {

      Books: 'Dan Brown: Inferno'

    }



    const [state, dispatch] = useReducer((state, action) => {

      switch(action.type) {

        case 'ADD_BOOK':

          return { Books: action.payload }

        default:

          return state

      }

    }, initialState);

    return (

        <div>

            <MyProvider value={{state, dispatch}} >

                <HFirstC />

                <HSecondC />

            </MyProvider>

        </div>

    )

}



render(<App />, document.getElementById('root'));

Copy the code

Results:

Mm-hmm 😊, so we have a state management

defects

  • Lack of time travel
  • Middleware not supported
  • Performance is poor

You can see the result above, a state change, all components re-rendered, MMMM 😊, so this is just a demo for fun, not for production

Finally, Redux’s answer:

useLayoutEffect

UseLayoutEffect handles side effects like useEffect and has the same function signature as useEffect, but it calls Effect synchronously after all DOM changes. You can use it to read DOM layouts and trigger rerenders synchronously. The update schedule inside useLayoutEffect is refreshed synchronously before the browser performs the drawing.

❗️ useEffect is recommended as far as possible, because useLayoutEffect, the callback function in useLayoutEffect, will execute immediately after the DOM update is complete, but will complete before the browser can do any drawing, blocking the browser drawing

The difference is that useEffect is asynchronous and useLayoutEffect is synchronous

Why use

Resolve some flicker scenarios

How to use

useLayoutEffect(fn, []) // Take two arguments: a callback function and an array parameter (representing dependencies)

Copy the code

Knowledge collection

⛽ ️ no…

Custom hooks

Custom Hooks are simple, we can use official Hooks to separate logic from reuse, that is, our custom Hooks. When you find a lot of code like this in a project, use Hooks

❗️ Earlier we analyzed patterns for state logic reuse, such as Mixin,HOC, and Render Props. Here are custom hooks for state logic reuse (😊 is finally coming to an end).

In terms of business, I classify custom Hooks into two categories: custom base Hooks and custom business Hooks

Business Hooks

Let’s say we have the same request method for multiple pages

// start with use

export const useUserData = (category: string[], labelName? : string) = > {

  const [baseTotal, setBaseTotal] = useState<number>(0)



  useEffect((a)= > {

    dealSearchTotal(category, labelName)

  }, [labelName, category])

  const dealSearchTotal = async (

) = > {

    const data = await getUserData(curCategory, labelName)

    const { baseTotal, calculateTotal, basicTotal, extTotal } = data

    setBaseTotal(baseTotal)

  }

  // Finally return the desired data

  return [baseTotal, calculateTotal, basicTotal, extTotal]

}

Copy the code

❗️ well If you notice that you are writing duplicate code, it is ok to isolate custom Hooks

Basic Hooks

Base Hooks are utility methods that are not normally relevant to the business

useEffectOnce

This Hooks is executed only once on a function component

const useEffectOnce = (effect) = > {

  useEffect(effect, []);

};



export default useEffectOnce;

Copy the code

useMount

This Hook is called when the component is mounted

const useMount = (fn) = > {

  useEffectOnce((a)= > {

    fn();

  });

};



export default useMount;

Copy the code

useUnmount

This Hook is called when the component is destroyed

const useUnmount = (fn: () = > any): void= > {

  const fnRef = useRef(fn);

  fnRef.current = fn;

  useEffectOnce((a)= > () => fnRef.current());

};



export default useUnmount;

Copy the code

usePrevious

Gets the old value of the component’s state or props

const usePrevious = (state): => {

  const ref = useRef();

  useEffect((a)= > {

    ref.current = state;

  });

  return ref.current;

};

export default usePrevious;

Copy the code

❗️ For other references Umi Hooks

The last