preface

Document Contents:

  1. Example references Refs
  2. Context Context
  3. Higher-order Components
  4. Hook Hooks

Refs

Refs provides a way to access DOM nodes or React elements created in the Render method.

This article is more about telling you what and how, rather than when and why.

The official documentation link is here

The usage process is as follows:

  1. Create an instance. Create a Refs instance, for examplethis.myRef = React.createRef()
  2. Mount the instance. Mount the instance created above to the target element via the ref attribute in the tag, for example<input ref={this.myRef}/>
  3. Access to the instance. Get the element by accessing the current attribute on the Refs instance, for examplethis.myRef.current.focus()

In fact, the following different forms, different ways, are the “three steps”, but “create”, “access” there are a variety of ways, only that

There is only one way to “mount” an instance, and that is to place it on the ref attribute of an element

Refs instances are created in the following ways:

  • React.createRef(Used in ClassComponent, introduced in React 16.3)
  • React.useRef(Used in FunctionComponent, introduced in React 16.8)
  • The callback Refs
  • String Refs (obsolete, forget her)

You can create instances in FunctionComponent, But you can’t mount an instance to the FunctionComponent (although you can make the FunctionComponent accept a ref property and pass it down via the react. forwardRef, it’s not actually mounted to the component).

React.createRef

CreateRef can only be used in ClassComponent because the API does not have the effect of Hooks, whose values are initialized as the FunctionComponent executes repeatedly.

Of course, if you must use it in FunctionComponent, you can also access mounted elements in current, but this will result in constant instantiation as the component renders.

This method, which is not Hooks, has to be concerned with “repeat execution”. In the following example, the react. createRef is called in the constructor and is guaranteed to be instantiated only once.

React passes a DOM element to the current property when the component is mounted and a NULL value when the component is unmounted. The ref is updated before the componentDidMount or componentDidUpdate lifecycle hook is triggered.

CreateRef creates and accesses react. createRef

class MyClassComponent extends React.Component {
  constructor(props) {
    super(props);
    this.myRef = React.createRef(); // instantiate a ref object
  }

  handleInputFocus = () = > {
    if (this.myRef.current) {
      /** * Myref.current does not necessarily have a value throughout the process * because, for example * - you instantiated a ref object, but you did not mount the object to the corresponding element * - or the element was removed at the moment * so when accessing ref, It is generally to judge whether there is a oh ~ */

      this.myRef.current.focus(); / / the focus

      // Current is an HTMLElement (e.target in event listener)
      console.log(this.myRef.current); }}render() {
    return (
      <div>
        <input/** * passes on the elementrefProperty will bemyRefThe incoming * is updated when the element is initialized or rerenderedmyRefThe value of the *myRefYou can think of it as an object where there's onecurrentProperty, that's what you update when the element changescurrentProperties of * *p.s."Element" can be"DOMNodes orReactComponents "* /ref={this.myRef}
        />
        <span/ / pointsspanWhen focusing on the topinput
          onClick={this.handleInputFocus}
        >Focusing on the</span>
      </div>); }}Copy the code

React.useRef

React.useRef is Hooks, which can only be used in FunctionComponent because Hooks cannot be used in ClassComponent.

When used in FunctionComponent, the react. useRef is initialized repeatedly as the component renders. This is also unique because Hooks are used in normal functions, but they perform more than normal functions in the React engine, such as initialize only once. Or reference invariant

let outterRef = null;

function MyFunctionComponent() {
  const [count, setCount] = React.useState(0);
  const innerRef = React.useRef(null);
  
  useEffect(
    () = > {
      // execute at initialization time
      outterRef = innerRef
    },
    []
  );
  
  useEffect(
    () = > {
      /** * this will always be true * because of the Hooks feature, which allows the same */ instance to be retrieved even after rerendering, i.e. after repeatedly calling react. useRef
      console.log(outterRef === innerRef)
    },
    [count]
  )
  
  return (
    <>
      <input ref={innerRef}/>
      <button onClick={()= > setCount(count+1)}>{count}</button>
    </>)}Copy the code

React Hooks if you don’t understand this, you can skip ahead to learn React Hooks first

The callback Refs

We pass a callback function to the ref attribute. React calls the callback function at different times, passing the element (or component) as an argument:

  • Before the mount
  • Before triggering the update
  • Before uninstallation (pass NULL)

Supports use within FunctionComponent and ClassComponent

// Used in ClassComponent
class MyClassComponent extends React.Component {
  constructor(props) {
    super(props);
    this.inputRef = null;
  }
  componentDidMount() {
    this.inputRef && this.inputRef.focus();
  }
  setMyRef = (ref) = > {
    this.inputRef = ref;
  }
  render() {
    return (
      <input type="text" ref={this.setMyRef}/>)}}// Used in FunctionComponent
function MyFuncComponent (props) {
  let inputRef = null;
  const handleClick = () = > {
    inputRef && inputRef.focus();
  }
  const setMyRef = (ref) = > {
    inputRef = ref
  }
  return (
    <div>
      <input type="text" ref={setMyRef}/>
      <button onClick={handleClick}>Focusing on the</button>
    </div>)}Copy the code

2. Context

Context provides a way to pass data across the component tree without manually adding props for each layer of components. In general, when an ancestor component wants to pass a value to a descendant component, it passes through layers of props, but this approach is extremely cumbersome. Context provides a way to share such values between components without explicitly passing props layer by layer through the component tree

Mobx and Redux state managers use Context to transfer data across components

There are currently two ways to use Context:

  • Broadcast mode (caution, you can’t handle this one)
  • Producers/consumers (introduced in React 16.3)

Broadcasting mode

There are two steps to use:

  • Provided: Settings in ancestor componentschildContextTypesgetChildContext
  • Get: declared in descendant componentscontextType

No more bb, here’s an example:

import React from "react";
import PropTypes from "prop-types"; // Prop-types is a built-in library in React

// Grandfather component
class CompGrandFather extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      products: []}}componentDidMount() {
    this.setState({
      products: [1.2.3.4]})}ChildContextTypes defines the type of data in the context passed down
  static childContextTypes = {
    myContextProducts: PropTypes.array,
    // myContextFunc: PropTypes. Func // prop-types
  };

  // Return the corresponding data (object) in getChildContext
  getChildContext() {
    return {
      myContextProducts: this.state.products,
    };
  }

  render() {
    return (
      <div>
        <CompFather>
          <CompChild/>
        </CompFather>
      </div>)}}// Parent component
function CompFather(props){
  console.log('CompFather re-render ')
  return (
    <div>{props.children}</div>)}// The child component
class CompChild extends React.Component {
  /** * Descendant components use contextTypes to specify which data the component needs to retrieve from the context
  static contextTypes = {
    myContextProducts: PropTypes.array,
  };

  render() {
    return (
      <div>
        {this.context.myContextProducts
          .map(id => (
            <p>{id}</p>
          ))}
      </div>)}}Copy the code

I believe that after you try to follow this example to write, you have basically mastered the use of ~

However, this approach is not officially recommended for projects for several reasons:

  • It broke the fractal architecture idea of React
    • Components cannot reuse the Context (if a Context is used in a component, it means that the ancestor component must pass the corresponding Context)
    • React can provide Context in any layer of ancestor components. If the current component provides the same Context repeatedly, it will overwrite the Context passed by the ancestor. The descendant component obtains the Context provided by the “nearest” ancestor component. There is no way to find the source of the Context data.
    • The process of passing the Context can be broken (if a component returns false in shouldComponentUpdate during Context passing, the following component will not be able to rerender, so that the new Context value cannot be updated to the following component)
  • Performance issues
    • React does not use the React reuse algorithm. (Once a node provides a Context, all its children are treated as side effects. React itself does not determine whether the children use the Context. And whether the provided Context has changed, so if a node provides a Context is detected, its children will be considered updated.)

Producer/consumer

React 16.3 uses the React Context to extend the functionality of the component by providing features to the component itself. These extensions have side effects. But in fact, we use Context only to solve the problem of data penetration, so some people propose to use the form of components to realize data transmission, respectively producer component and Consumer component. Changing the data provided by the Provider only triggers the Consumer to rerender.

The Production/consumer model is a design pattern that addresses the strong coupling between the producer and the consumer through a third party container, where the producer only produces and pushes data to the third party container, and the consumer only retrieves data from the third party container.

The specific implementation varies from language to language, so instead of trying to get to the point, try to think about the advantages of this problem solving approach from an OOP perspective, and hopefully you can understand the various classic design patterns in object-oriented programming

A preview of the keywords involved:

  • React.createContext
    • Context.Provider
    • Context.Consumer
  • Class.contextType

Write an example using the second generation context:

/** * Create a Context object using the createContext method * that takes a single parameter, which can be any value. I prefer to pass an object here because it is easier to expand * P.S. MyCtx has two properties that come from a component: * -myctx. Provider producer * -myctx. Consumer **/
const MyCtx = React.createContext({
  innerProducts: [].innerName: 'Default name'.innerAge: 0
})

// Grandfather component
class CompGrandFather extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      products: []}}componentDidMount() {
    this.setState({
      products: [1.2.3.4]})}render() {
    return (
      <MyCtx.Provider
        value={{// Place the value you want to pass invalueOn the propertiesinnerProducts: this.state.products, / /innerName:'Zhang Cannon ', // The default value will be used when one parameter is missinginnerAge: 18
        }}
      >
        <CompFather>
          <CompChild/>
        </CompFather>
      </MyCtx.Provider>)}}// Parent component
function CompFather(props) {
  // Interestingly, CompGrandFather does not rerender CompFather by changing the context
  console.log('CompFather re-render ')
  return (
    <div>{props.children}</div>)}// The child component
function CompChild(props) {
  return (
    <div>
      <MyCtx.Consumer>{(CTX) => {/** * Consumer props. Children is a method that accepts a value passed by the Provider * when the value of the Provider changes, The Consumer calls props. Children again and passes the new value **/ return (<div>
              {ctx.innerProducts
                .map(id => (
                  <p>{id}</p>
                ))}
              <p>Default value demo: {ctx.innerName}</p>
            </div>)}}</MyCtx.Consumer>
    </div>)}Copy the code

To sum up, there are three processes to use:

  1. useReact.createContextCreate a component-independent instance of a state machine with default values. The instance has two properties, both of which are React componentsProviderConsumer
  2. Used in the ancestor componentProviderComponent to which data is passed
  3. Used in descendant componentsConsumerComponent from which to obtain data

Class.contextType

React provides a class. contextType to retrieve data from the Consumer component.

The contextType property mounted on the class is reassigned to a Context object created by react.createcontext (). This property allows you to use this.context to consume the value of the most recent context. You can access it in any lifecycle, including the Render function.

Reimplement the above child component using class.contextType:

class CompChild extends React.Component {
  static contextType = MyCtx; // Declare the associated Context in the contextType static property
  componentDidMount() {
    console.log("You can also get the context during the life cycle.".this.context)
  }
  render() {
    const {innerProducts, innerName} = this.context;
    return (
      <div>
        <div>
          {innerProducts
            .map(id => (
              <p>{id}</p>
            ))}
          <p>Default value demo: {innerName}</p>
        </div>
      </div>)}}Copy the code

Third, high-level component HOC

HOC is an advanced technique used in React to reuse component logic. HOC itself is not part of the React API; it is a design pattern based on the composite features of React.

A higher-order component is simply a function that takes a component as an argument and returns a new component. A higher-order component is a higher-order function:

  • Components (functions) are passed as arguments
  • The component (function) is output as return value

The React component is a function.

Function MyReactComp(){return (<p> component </p>)}

Component factory

There are two main ways to implement HOC:

  • Property proxy (function returns a component that we define ourselves, the props passed from the proxy layer)
  • Reverse inheritance (returns a component that inherits from the original component and uses super to access the render of the original component)

Here’s an example of how to create and use HOC using a “property proxy” :

// Have a weapon (common component)
function Weapon(props) {
  return (
    <div>
      <p>Name: {props. The name}</p>
      <p>Level: {props. Level}</p>
      <p>Tags: {props. Effect}</p>
    </div>)}/** * This higher-order component takes two arguments, where NormalComp is component **/
function WithEffectHOC(NormalComp, effect) {
  // Returns a new component
  return function(props) {
    /** * props * this is just through {... Props} writing transfer upper props of deconstruction and transfer all of its down intact * written below, write effect to write props of deconstruction, so if the upper conveyed props also contains effect attributes, This will override the effect written earlier This is just passing it all in, but in practice, you're doing all sorts of props
    return (
      <NormalComp
        effect={effect}
        {. props} / >)}}/** * "expands" on different Weapon types via WithEffectHOC * and ends up with two new components **/
const WeaponLight = WithEffectHOC(Weapon, 'luminous')
const WeaponDark = WithEffectHOC(Weapon, 'Dark Edition')

function App() {
  return (
    <div>
      <WeaponLight name="A weapons" level="99"/>
      <WeaponDark name=Weapons "B" level="10"/>

      <WeaponLight name=Weapons "C" level="98" effect="Not just any glow."/>
    </div>)}Copy the code

enhancements

Extract some common logic, construct a high-level component, and then determine whether common components need to be “upgraded” through this high-level component according to the needs of the business, for example:

  • Additional life cycles
  • Additional events
  • Additional business logic

A simple example is the burial point:

function WithSentryHOC (InnerComp) {
  return class extends React.Component {
    myDivRef = React.createRef()
    componentDidMount() {
      this.myDivRef.current.addEventListener('click'.this.handleClick)
    }
    componentWillUnmount() {
      this.myDivRef.current.removeEventListener('click'.this.handleClick)
    }
    handleClick = () = > {
      console.log('Send buried point: click on itThe ${this.props.name}Component `)}render() {
      return (
        <div ref={this.myDivRef}>
          <InnerComp {. this.props} / >
        </div>)}}}function MyNormalComp (props) {
  return (
    <div>Common components</div>)}/** * Update the MyNormalComp component * every time you click on the component console.log * For MyNormalComp components, this function is "unknown" **/
const MyCompWithSentry = WithSentryHOC(MyNormalComp);

function App(){
  return (
    <MyCompWithSentry name="One of my components"/>)}Copy the code

Rendering hijacked

HOC can not only expand the function of the original components, but also add conditional judgment to modify the rendering results

The following uses a simple demo to demonstrate how to achieve delay rendering:

function WithDelayRenderHOC (InnerComp) {
  return class extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        show: false}}componentDidMount(){
      window.setTimeout(() = > {
        this.setState({
          show: true})},3000)}render() {
      // When certain conditions no longer render InnerComp
      if (!this.state.show) {
        return <div>Wait...</div>
      }
      return <InnerComp {. this.props} / >}}}Copy the code

conclusion

At present, only a few typical scenarios are selected to demonstrate. In practical use, designing a HOC is often not so simple. This, in turn, relates to the idea of section-oriented programming (AOP), where the main role of AOP is to extract functionality unrelated to the core business logic module and then “dynamically weave” it into the business module.


B: Yes

Hooks are new in React 16.8. It lets you use state and other React features without having to write a class.

Historically writing components using Class Component had the following problems:

  • Reusing state logic between components is difficult
  • Complex components become difficult to understand

In the past, project code was often divided into “code mountains” based on the life cycle of components. Now, components are broken down into smaller functions (like the Mobx Store). React provides Hooks for things like life cycle listening. This splits the code into business logic.

The following is a short example of how to use Hooks in common use:

  • React.useStateState of the hook
  • React.useEffectSide effect hook
  • React.useCallbackThe callback function hook
  • React.useContextContext hooks (mentioned earlier)
  • React.useRefAccess hooks (mentioned earlier)

In React, Hooks are fixed from the Hooks API. Use this article to learn how to use Hooks

React.useState

UseState returns an array with the first element being the current value and the second element being the set method by calling the react. useState method and passing in arguments as defaults

class MyClassComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0,
    }
  }
  handleClick = () = > {
    this.setState({
      count: this.state.count + 1})}render(){
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={this.handleClick}>Click on the</button>
      </div>)}}// Write a component that has the same effect using Hooks as well
function MyFuncComponent() {
  // Declare a state variable called "count".
  const [count, setCount] = React.useState(0);
  return (
    <div>
      <p>You hit {count} times</p>
      <button onClick={()= >SetCount (count + 1)}> Click</button>
    </div>
  );
}
Copy the code

React.useEffect

Usage:

React.useEffect(() = > {
  // do something
  return () = > {
    // trigger when unmount
  }
}, [dependencies])
Copy the code

React.useEffect takes two arguments:

  • Function, will be fired at a particular time
  • Array, is a dependency, that is, when the data in the dependency changes, will fire the function passed by the first parameter
    • No arguments are passed and are executed every time you re-render
    • Pass a non-empty array, which is executed when one of the items changes
    • Pass an empty array, only when the component is mounted and unmounted

Also, the first argument returns a function that will be executed before the component is unmounted

function Welcome(props) {
  useEffect(() = > {
    // This function is executed again each time the component is re-rendered
    document.title = 'Load complete';
  });
  return <p>Hello</p>;
}
Copy the code

React.useCallback

Returns a Memoized callback function.

function MyFuncComp(props){
  const [count, setCount] = React.useState(0);
  const handleClick = () = > setCount(count + 1)
  return (
    <div>
      <p>You hit {count} times</p>
      <button onClick={handleClick}>Click on the</button>
    </div>
  );
}
Copy the code

In the example above, handleClick was redeclared every time MyFuncComp was re-rendered, and most fatally, the onClick bound to the div was different every time, which would result in unnecessary re-rendering!

React will have to solve this problem since it writes components in FunctionComponent mode, so React.usecallback and a series of memoized hooks came into being.

To rewrite the previous example:

function MyFuncComp(props){
  const [count, setCount] = React.useState(0);
  const handleClick = React.useCallback(
    () = > setCount(count + 1),
    [count],
  );
  return (
    <div>
      <p>You hit {count} times</p>
      <button onClick={handleClick}>Click on the</button>
    </div>
  );
}
Copy the code

Customize the Hook

With custom hooks, component logic can be extracted into reusable functions.

For example, the “get current browser size (while listening for resize)” part of the logic is packaged into a custom Hook that can be used by different components simultaneously:


P.S. hooks can also use other hooks internally, constantly nesting **/
function useWindowSize() {

  Use react. useState to declare a variable
  const [windowSize, setWindowSize] = React.useState<IWindowSize>({
    width: document.documentElement.clientWidth,
    height: document.documentElement.clientHeight,
  });

  // Use React. UseCallback to declare a callback function
  const onResize = React.useCallback(() = > {
    setWindowSize({
      width: document.documentElement.clientWidth,
      height: document.documentElement.clientHeight, }); } []);// Use react. useEffect to trigger event binding
  React.useEffect(() = > {
    window.addEventListener('resize', onResize);
    return () = > {
      // Remove the listener when unmount
      window.removeEventListener('resize', onResize);
    };
  }, [onResize]);

  return windowSize; // Return only values (do not return set methods)
}

/ / component A
function MyCompA() {
  const windowSize = useWindowSize();
  return (
    <div>
      <p>Component A</p>
      <p>Width: {windowSize. Width}</p>
      <p>Height: {windowSize. Height}</p>
    </div>)}// Component B, used with other hooks
function MyCompB(props){
  const [count, setCount] = React.useState(0);
  const handleClick = React.useCallback(
    () = > setCount(count + 1),
    [count],
  );
  const windowSize = useWindowSize();
  return (
    <div>
      <p>You hit {count} times</p>
      <button onClick={handleClick}>Click on the</button>
      <p>Width: {windowSize. Width}</p>
      <p>Height: {windowSize. Height}</p>
    </div>
  );
}
Copy the code

If the different business or functional logic is encapsulated into a Hook, and then only one call in the component, without caring about the internal logic, the coding style of logic tiling can be realized