The official definition of Hooks

Hook is a new feature in React 16.8. It lets you use state and other React features without having to write a class.

React version >= 16.8; use Hooks to write React code. Understand closures. Understand the behavior of components.

useState

Before Hooks, the problem with function components was that they could only be “pure” defined, had no internal state and could only accept external data passed in from outside. UseState dealt with internal state.

const App = () = > {
  const [num, setNum] = useState(0);
  console.log(num);
  return (
    <div className="App">
      <span>{num}</span>
      <button onClick={()= > setNum(num + 1)}>add</button>
    </div>
  );
};
// The page will render 0
// Each time the button is clicked, the page is re-rendered and updated with the latest value of the output num+1
Copy the code

This code, as in the Class component, defines state, presses num+1, rerenders the page and prints the latest num value. Unlike class components, function components execute the function from scratch every time they re-render. Class components only execute the corresponding declare hook and render function.

Easy implementation of customUseState

Try to understand how useState works with a “less mature” example

const _states = []; // Used to store state
let _index = 0; // Used to store initialization subscripts
const useCustomUseState = (initialValue) = > {
  const [_, reRender] = useState({}); // This is a tool function that can refresh components
  const currentIndex = _index; // Save the current index
  _index += 1; / / I + 1
  _states[currentIndex] = _states[currentIndex] || initialValue;
  // Determine if the current subscript element has a value, update _state with initialization state and subsequent function re-render execution
  const setState = (newValue) = > {
    // The function updates the state value
    _states[currentIndex] = newValue;
    _render();
  };
  const _render = () = > {
    // As soon as the value is updated, the index is reset because the app will re-render and then execute the useCustomUseState function in sequence
    // The values in _state can only be retrieved in the order in which they are executed
    _index = 0;
    reRender({});
  };
  // Return the current passed state and the updated state to the consumer
  return [_states[currentIndex], setState];
};

function App() {
  const [num, setNum] = useCustomUseState(0);
  const [aaa, setAaa] = useCustomUseState(111);
  // Here we use our own implementation of useState
  console.log(num);
  console.log(aaa);
  return (
    <div>
      {num}
      <button onClick={()= > setNum(num + 1)}>add one </button>
      <hr />
      {aaa}
      <button onClick={()= > setAaa(aaa + 1)}>add one </button>
    </div>
  );
}
Copy the code

Clicking on both buttons will give the corresponding value to +1 and re-render the latest state value. Const currentIndex = _index; Caches the currently executed subscript, the currentIndex variable and the update function that’s about to return form a closure, so that even if _index changes, currentIndex will always be the _index that we got when we ran the function, React stores the _states and _index corresponding to the example on the component node. After understanding this basic implementation, the feeling of seeing useState will be much more transparent.

There are a few caveats to using useState

  1. Do not attempt to obtain updated values in the current business function
  2. useStateThe order of execution each timere-renderThey all have to be the same, which means they don’tif else, otherwiseReactcomplains
  3. useStateDon’t likeClassComponents merge automatically like thatstate, which requires manual operation
  4. useStateYou can receive a function to perform the update operation

1. Do not attempt to obtain updated values in the current business function

function App() {
  const [num, setNum] = useCustomUseState(0);
  const addNum = () = > {
    setNum(num + 1);
    setTimeout(() = > { console.log("num:" + num); }, 1000)};return <button onClick={addNum}>add one </button>
}
Copy the code

After this code is clicked on the button for one second, the output value will always be the same as before the update. The code is executed synchronously, but the setState update reexecutes App. When the setTimeout function is executed before the update, the num variable before the update of the current component and the arrow function passed in by setTimeout form a closure. The old “num” is 0 and the new “num” is 1. The arrow function takes the old “num” variable, so the output num will be 0 after clicking the button for 1 second. (Closure and closure and closure…)

2. UseState must be executed in the same order every time render, that is, cannot be written inif elseOr React will report an error

  const [num, setNum] = useState(0);
  if(num === 0) {const [aaa, setAaa] = useState(111);
  }
  const [c, setC] = useState(0);
  
// React generates the following error
// React Hook "useState" is called conditionally. React Hooks must be called in the exact same order in every component render
Copy the code

If the order of execution is not guaranteed, we call useState three times on the first execution of the function and return the correct value, the _states array that holds the data is now 3 in length. The second execution of the function called useState was twice. The c variable should have been _states[2], but useState was executed once less because the conditions were not met. As a result, when the function returned C was executed, it got _states[1], which would cause data problems after refreshing.

3. UseState does not automatically merge state like the Class component does

function App() {
  const [person, setPerson] = useState({
    name: "libai".age: 89});return (
    <div>
      {person.name}
      {person.age}
      <button onClick={()= > setPerson({ age: 100 })}>changeAge</button>
    </div>
  );
}
Copy the code

When you click the button, you will find that the libai in the page is missing, because there is no name attribute in the object passed in by the update. The name attribute will overwrite the old value and will not be merged automatically. After the rendering, the value of the person object is {age:100}, and the person. It’s legal, but it doesn’t output anything. Just take advantage of… Expand the operator or object. assign to extract all the keys of the old Object and assign a new value, which contains all the attributes of the old value.

function App() {
  const [person, setPerson] = useState({
    name: "libai".age: 89});const changePerson = () = >{
      // setPerson({ ... person,age: 100 });
      // if the effect is equivalent, then one is used
      const newPerson = Object.assign({},person,{age:100});
      setPerson(newPerson);
  }
  return (
    <div>
      {person.name}
      {person.age}
      <button onClick={changePerson}>changeAge</button>
    </div>
  );
}
Copy the code

4. UseState can receive a function to perform the update operation

function App() {
  const [person, setPerson] = useState({
    name: "libai".age: 89});const changePerson = () = >{ setPerson({ ... person,age: person.age + 10}); setPerson({ ... person,age: person.age + 10}); setPerson({ ... person,age: person.age + 10}); setPerson({ ... person,age: person.age + 10 });
  };
  return (
    <div>
      {person.name}
      {person.age}
      <button onClick={changePerson}>changeAge</button>
    </div>
  );
}
Copy the code

In this code, after clicking the button, the code appears to perform four +10 operations of 89 to get 129, but the actual result is 89, which is caused by closure (tm closure again). As I mentioned, the code executes synchronously, but the update is asynchronous. SetPerson executes four times, and the parameter object Person. age gets 89. So all four functions pass in objects with an age of 99 after +10. If you want to solve this problem, you can update state by passing in a function. Make minor changes to the click event

  const changePerson = () = > {
    setPerson((person= > ({ ...person, age: person.age + 10 })));
    setPerson((person= > ({ ...person, age: person.age + 10 })));
    setPerson((person= > ({ ...person, age: person.age + 10 })));
    setPerson((person= > ({ ...person, age: person.age + 10 })));
  };
Copy the code

The first argument to the function is the value of the “current “state. This” current “is not the value of the state in the component (the person variable), but the actual, most recent value of the state. The first time you do setPerson, the age of state is 89, updating the object; The second time the person is set, the age of state is 99, and so on, the final component gets an age of 129. In theory, the first use of this update method, the skull can be less pain point 🙂

useReducer

In fact, it is a complex version of useState, through some limited operations, to achieve the purpose of “specification update”, useReducer accepts a function and an initial value, get read and write API, directly on the code

const initialValue = {
  name: "".age: 0};const reducer = (state, { type, payload }) = > {
  switch (type) {
    case "changeName":
      return {
        ...state,
        name: payload.name,
      };
    case "changeAge":
      return {
        ...state,
        age: payload.age,
      };
    default:
      throw new Error("What the hell type are you passing in?"); }};function App() {
  console.log("app running");
  const [state, dispatch] = useReducer(reducer, initialValue);

  const submitForm = (evt) = > {
    evt.preventDefault();
    console.log(state);
  };

  return (
    <form onSubmit={submitForm}>
      <label htmlFor="nage">Name:<input
          id="name"
          value={state.name}
          onChange={(evt)= >
            dispatch({
              type: "changeName",
              payload: { name: evt.target.value },
            })
          }
        />
      </label>

      <label htmlFor="age">Age:<input
          type={"number"}
          id="age"
          value={state.age}
          onChange={(evt)= >
            dispatch({
              type: "changeAge",
              payload: { age: evt.target.value },
            })
          }
        />
      </label>
      <button onClick={()= >dispatch({type:"have luncher"})}>test Error</button>
      <button type={"submit"} >submit</button>
    </form>
  );
}
Copy the code
  • useReducerAfter passing in the function and the initial value, returns the value anduseStateSimilarly, the first entry of the array is initializedstateThe second item is for updatingstateThe function of
  • When the name field and age field are modified, both actually trigger a component re-rendering in theAppThere is a print line in the function body, which prints print each time it is typed, and the update operation is passeddispatchFunction to triggersetState), and then we do what we defined in the beginningreducerFunction and willstateValues anddispatchThe incoming parameter is passed twice, updating the current according to predefined rulesstate.
  • When you clicksubmitButton, the console outputs the currentstateThe value of the object
  • When the incoming errortype“, such as in codetestErrorThe button click event throws a predefined exception

In short, useReducer can be used to manage complex internal state changes in a more standardized way (complex version useState).

useEffect

ComponentDidMount. ComponentDidUpdate. ComponentWillUnmout. It takes a function and an optional argument (array) as arguments. If multiple Useeffects exist in a function, they are executed in the order in which they occur.

simulationcomponentDidMountrole

The second argument, passing in an empty array means that there are no dependencies, so the contents of the function will not be executed again when the App is re-rendered

function App() {
  const [num, setNum] = useState(0);
  useEffect(() = > {
    console.log("effect running");
    const divEle = document.querySelector("#hook");
    divEle.style.color = "red"; } []);return (
    <>
      {num}
      <button onClick={()= > setNum(num + 1)}>re-render</button>
      <div id="hook">hi,hooks</div>
    </>
  );
}
Copy the code
  • Gets when the page is renderedidforhookthedivAnd itsstylethecolorAttribute toredBecause you can get itdomThe element can also testify to its execution timing when the page is rendered, without rendering words to get a lonely?)

  • Click on there-renderThe button will be updatednumAnd execute againAppFunction, look at the console"effect running"It only prints once, no matter how many times it’s clickedre-renderButton, will not be executeduseEffectThe callback function of

simulationcomponentDidUpdaterole

The second argument, passing in an array of dependent values, is used to indicate that when one of the items in the array changes, the callback function is executed again. In the example code, we pass in num as the dependency

function App() {
  const [num, setNum] = useState(0);
  useEffect(() = > {
    console.log("effect running");
  }, [num]);
  return (
    <>
      {num}
      <button onClick={()= > setNum(num + 1)}>re-render</button>
    </>
  );
}
Copy the code
  • Gets when the page is renderedidforhookthedivAnd itsstylethecolorAttribute toredBecause you can get itdomThe element can also testify to its execution timing when the page is rendered, without rendering words to get a lonely?)

  • Each click, except that the page will print once after renderingre-renderButton, it updatesnum, and since num is passed in as a dependency, the callback is executed again
When the dependency is not passed, the callback function is executed after any state is updated
  useEffect(() = > {
    console.log("effect running");
  });
Copy the code

As currently expected, incoming dependencies and non-passed dependencies behave the same, and the log is printed again when the button is clicked

simulationcomponentWillUnmountrole

function App() {
  const [childVisible, setChildVisible] = useState(true);
  return (
    <>
      <button onClick={()= >setChildVisible(! childVisible)}>toggle</button>
      {childVisible && <ChildTest />}
    </>
  );
}

function ChildTest() {
  const [num, setNum] = useState(0);
  useEffect(() = > {
    console.log("effect running");
    return () = > {
      console.log("component unmount");
    };
  }, [num]);

  return (
    <>
      {num}
      <button onClick={()= > setNum(num + 1)}>re-render</button>
    </>
  );
}
Copy the code

In the useEffect callback, the return function is executed when the component is uninstalled. In the above code, every time you click the button, the page is uninstalled and rendered again. Click the toggle button, If childVisible is false and the condition is not met, the ChildTest component will not be rendered and the return function will be executed when uninstalled

useLayoutEffect

This hooks are timed to execute before the browser renders, before the div is visible to the browser

Graph TD useLayoutEffect --> Browser render --> useEffect
function App() {
  useEffect(() = > {
    console.log("useeffect"); } []); useLayoutEffect(() = > {
    console.log("useLayoutEffect");
    document.querySelector("#text").innerHTML = "One, two, three, four, five, six, seven, eight, ninety."; } []);return (
    <>
      <div id="text">123456789</div>
    </>
  );
}
Copy the code

This code first prints console.log(“useLayoutEffect”) when the App is rendered; UseEffect console.log(” useEffect “); useEffect console.log(” useEffect “); UseEffect is always recommended unless you really need to change the layout before rendering the page

useContext

Context is used to represent a context, a global variable is a global context, and a context is a local global variable

Context I’ve already blogged about it, but you can use the React Context link in 3 safe ways: ) To take an analogy, it is to specify the scope of provision with the provided containers, providing all components within the scope of food materials, tableware, gas, POTS and pans, Nine Yin Zhen Jing, I Ching, etc. If the door is closed, only POTS and pans can be provided. Consumers can buy food materials and learn cooking by themselves. Any wrapped component can share the things (objects) and actions (methods) provided by the container.

Note that if you change the value of the share, no refresh is triggered (not reactive)

Context can replace Redux

useRef

If you need a value that will remain unique throughout the render process, you can use useRef, which can be used to store Dom objects or any object. This value will not change as the component updates. The return value is {current:x}, and the object’s current property holds the target value. Why is it necessary to pack a layer of current? Because only objects can keep their addresses the same object is always accessible.

Examples of reference objects

function App() {
  const crf = React.createRef();
  const urf = useRef();
  const showAllRef = () = > {
    console.log(crf.current);
    console.log(urf.current);
  };
  return (
    <>
      <div ref={crf}>createRef</div>
      <div ref={urf}>useRef</div>
      <button onClick={showAllRef}>showAllRef</button>
    </>
  );
}
Copy the code

The code uses createRef and useRef to refer to dom elements, respectively, and when the showAllRef button is clicked, the corresponding DOM instance is printed to the console. In this case, they are equivalent. However, there is a problem with using createRef in function components because every time the component is refreshed, The urF variable is reassigned by the return value of createRef. Do not use creatRef when storing mutable objects. Using useRef to maintain state without triggering a refresh, which counts the number of times the Add button is clicked in the example, can be used as a global object within a component for the lifetime of the component (analogous to a window object that exists only within the component)

function App() {
  let count = useRef(0);
  const [num, setNum] = useState(100);
  useEffect(() = > {
    if (num > 100) {
      count.current += 1;
    }
  }, [num]);
  return (
    <>
      {num}
      <button onClick={()= > setNum(num + 100)}>add 100</button>
      <button onClick={()= > console.log(count.current)}>showCount</button>
    </>
  );
}
Copy the code

When the forwardRef says ref, you have to mention the forwardRef

In a function component, props cannot pass a ref reference, forcing it to throw a warning

react-dom.development.js:67 Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
Copy the code

The React. ForwardRef

const App = () = > {
  const buttonRef = useRef(null);

  const clickChild = () = > {
    buttonRef.current.click();
  };
  return (
    <>
      <Child ref={buttonRef} />
      <button onClick={clickChild}>Clicking will invoke the child component's click event</button>
    </>
  );
};

const Child = React.forwardRef((props, ref) = > {
  return (
    <div>
      <button ref={ref} onClick={()= >Console. log("click")}> The button that will be referenced by the parent component</button>
    </div>
  );
});
Copy the code

The forwardRef returns a component that receives a ref. The function that returns the component is called a higher-order component. The ref is then forwarded, and the Child component gets the ref reference from the second argument and uses it in the button example object, the button instance object of the buttonRef.current === Child component

useImperativeHandle

UseImperativeHandle allows you to customize the instance value exposed to the parent component when using a ref. In most cases, imperative code like ref should be avoided. UseImperativeHandle should be used together with the forwardRef.

What the ref returns to the parent component is up to you. Normally, ref.current is the target DOM instance object. The useImperativeHandle is used to customize the return value of ref. The parent component needs to get a reference to the DOM through ref.current

const App = () = > {
  const buttonRef = useRef(null);

  const clickChild = () = > {
    console.log(buttonRef.current);
  };
  return (
    <>
      <Child ref={buttonRef} />
      <button onClick={clickChild}>Click I'll print the ref object for the child component</button>
    </>
  );
};

const Child = React.forwardRef((props, ref) = > {
  const buttonRef = createRef(null);
  useImperativeHandle(ref, () = > {
    return {
      msg: "What the hell are you? You're not allowed to reference it. Forget it..amd: buttonRef.current,
    };
  });
  return (
    <div>
      <button ref={buttonRef} onClick={()= >Console. log("click")}> The button that will be referenced by the parent component</button>
    </div>
  );
});
Copy the code

{MSG :” XXX “, AMD :button Instance}

React.memo && React.useMemo && useCallback

If you click the changeState button on the React. Mome, the props for the passed Child will still be updated. If the props for the passed Child are not changed, the props will be updated

function App() {
  const [childProps] = useState(0);
  const [text, setText] = useState("text");
  return (
    <>
      <div>{text}</div>
      <button onClick={()= > setText(text + "text")}>changeState</button>
      <hr />
      <Child childProps={childProps} />
    </>
  );
}

const Child = (props) = > {
  console.log("I ran it.");
  return <div>child:props.m:{props.childProps}</div>;
};
Copy the code

By default, the high-order component wrapped in React. Memo compares props twice when the app render is performed. If the app render is the same, the props will not be updated. Accepts two parameters, the old props and the new props, and returns Boolean to determine whether to update the component

propsAreEqual? :(prevProps: Readonly<PropsWithChildren<P>>, nextProps: Readonly<PropsWithChildren<P>>) = > boolean
Copy the code

The function returns true to stop updating, and false to update

const Child = (props) = > {
  console.log("I ran it.");
  return <div>child:props.m:{props.childProps}</div>;
};

function App() {
  const [childProps] = useState(0);
  const [text, setText] = useState("text");
  return (
    <>
      <div>{text}</div>
      <button onClick={()= > setText(text + "text")}>changeState</button>
      <hr />
      <Child childProps={childProps} />
    </>
  );
}

const Child = React.memo((props) = > {
  console.log("I ran it.");
  return <div>child:props.m:{props.childProps}</div>;
});
Copy the code

If a function is passed to the props, the function is reassigned when the parent component is updated. The functions are the same, but the two functions are different, and the child component is re-rendered. In this case, useMemo is required. The first parameter of useMemo takes a function that returns the cached result, and the second parameter is the dependency array, which is recalculated only when the dependency changes.

function App() {
  const [childProps] = useState(0);
  const [text, setText] = useState("text");
  const fn = useMemo(() = > {
    return () = > {};
  }, [childProps]);
  return (
    <>
      <div>{text}</div>
      <button onClick={()= > setText(text + "text")}>changeState</button>
      <hr />
      <Child childProps={childProps} setText={fn} />
    </>
  );
}
Copy the code

Now when the parent component is updated, the child component will not be updated, because fn is cached, pointing to the same function address. UseMemo does not cache functions only, but can cache any data. If you feel that the writing of the cache function is a little bit complicated, you need to write a function and return a function. You don’t need to return a function in a function, you just need to pass in a function, which is a layer less than useMemo

React implements a syntax sugar
function App() {
  const [childProps] = useState(0);
  const [text, setText] = useState("text");
  const fn = useCallback(() = > {}, [childProps]);
  return (
    <>
      <div>{text}</div>
      <button onClick={()= > setText(text + "text")}>changeState</button>
      <hr />
      <Child childProps={childProps} setText={fn} />
    </>
  );
}
Copy the code

UseCallback (fn, deps) is equivalent to useMemo(() => FN, deps).

Custom Hooks

Finally, custom Hooks, which, like building blocks, take the logic of state out of the system, package it and use it, are entirely up to the player, the soul of Hooks.

const useUserList = () = > {
  const [list] = useState([]);
  const [loading] = useState(true);
  useEffect(() = > {
    // Suppose an Http request is made for a list
    setTimeout(() = > {
      setList([1.2.3]);
      setLoading(false);
    }, 2000); } []);return [list, loading, setList, setLoading];
};

const App = () = > {
  const [userList, loading] = useUserList();
  return (
    <>
      {userList.length
        ? userList.map((user) => <span key={user}>User: {user}</span>)
        : null}
      {loading && <div>In the load</div>}
    </>
  );
};
Copy the code
  • This encapsulates a customizationhooknameduseUserList.ReactHas a regulationhooksHave to beuseI’m going to name it at the beginning, and I’m just going to show you how it works, but I could have created a new onehooksFolders for each functionhookEncapsulate it and export it, of the components we need to use, we only need to introduce thishookFunction, and just call it to get the data and behavior we want, and writeclassCompared to components, state is separated from components and code can be reused.
  • demoSimulated ahttpRequest, return data two seconds later, only so you can get the data and update the component, becausehookIn the setStateIt’s actually executed inside the component, written outside the component, executed inside the component.
  • It is worth mentioning that,hookCan only be used in function components orUse the beginningFunction, otherwiseReactComplains.

Now that you’ve “gotten started” on the React Hooks feature, thanks for watching: