编程知识 cdmana.com

The method of calling subcomponents by react and the error of imperative programming

8d19cf2cbf295ac3f2fe2010e041d51b.png


This article will elaborate on the following :

  1. call DOM Element method
  2. call React Two direct solutions to the subcomponent approach
  3. Self examine whether the structural design of components is reasonable -- This paper discusses the relationship between declarative programming and imperative programming React Problems in development
  4. call React The best solution to the subcomponent approach
  5. summary


Basic needs : call DOM Elements Methods

Use native JavaScript In development , The page calls an active html The method of selecting elements is   Very common operation

For example, after the button below is hit , In addition to increasing the value of the counter , The cursor will automatically appear in the input box

<body>
<div>
    Counter: <span id="counter">0</span>
</div>
<br/>
<button id="increase-button">Click To Increase Counter and Focus Input</button>
<br/>
<br/>
<label for="input">
    Name:
</label>
<input id="input"/>

</body>
<script>
  const counter = document.querySelector("#counter");
  const input = document.querySelector("#input");

  document.querySelector("#increase-button").addEventListener("click", e => {
    counter.innerText = (parseInt(counter.innerText) + 1).toString();
    input.focus(); //  call input Of focus Method 
  });

</script>

1b58c561f51ca1d8fe128e78b6c6fdf1.jpeg


Look again. , Use React How to achieve the same effect

const Counter = () => {
  const [counter, setCounter] = useState(0);
  const inputRef = useRef(); //  The key  1
  const increaseHandler = () => {
    setCounter(counter + 1);
    inputRef.current.focus(); //  The key  2
  }

  return (
      <div>
        <div>
          Counter: <span>{counter}</span>
        </div>
        <br/>
        <button onClick={increaseHandler}>Click To Increase Counter and Focus Input</button>
        <br/>
        <br/>
        <label htmlFor="input">
          Name:
        </label>
        <input id="input" ref={inputRef}/> // //  The key  3
      </div>
  );
};

export default Counter;

There are three key points in the above code :

  1. Use useRef hook, Produce a ref
  2. stay button click handler Call inside inputRef.current.focus()
  3. stay input Add one more ref={inputRef} Properties of


Advanced demand : call React Method of subcomponent

above input Is a basic DOM Elements , So the method that calls it looks like The logic is intuitive . however , If you put input Switch to  React Components , And how to achieve it ?

Suppose there is one of the following React Component name ColorLight Simulate a colored light , Every time you click, you change the color

913c0e4401189ec8b24035b12c1c4b65.gif

We want to put this ColorLight Replace the above input , So that every time the button is clicked , In addition to increasing the value of the counter , It also automatically changes ColorLight The color of the

import React, {useRef, useState} from 'react';
import ColorLight from './ColorLight';

const Counter2 = () => {
  const [counter, setCounter] = useState(0);
  const ref = useRef();
  const increaseHandler = () => {
    setCounter(counter + 1);
    ref.current.changeColor(); //  change  ColorLight  The color of the 
  }

  return (
      <div>
        <div>
          Counter: <span>{counter}</span>
        </div>
        <br/>
        <button onClick={increaseHandler}>Click To Increase Counter and Focus Input</button>
        <br/>
        <br/>
        <ColorLight ref={ref}/>  // ref  quote   Customize  React  Components 
      </div>
  );
};

export default Counter2;

295f42faa558f57c7cd8b0346c64f757.gif

The code above is   The key —— How to make ColorLight To have a changeColor Methods?

Take a look back. We usually Write React Methods inside components Usually

  1. Self marketing , Like the one above button Of increaseHandler
  2. Pass to the child component to use , such as :
const Counter3 = () => {
  const [counter, setCounter] = useState(0);
  const inputRef = useRef();
  const increaseHandler = () => {
    setCounter(counter + 1);
  }

  return (
      <div>
        <div>
          Counter: <span>{counter}</span>
        </div>

        <AnotherChild onIncreaseCounter={increaseHandler} /> //
      </div>
  );
};

export default Counter3;

AnotherChild This component can call its props.onIncreaseCounter() To add a parent component ( namely Counter3) Of count value


The above two usages are React Most commonly used in development , But it doesn't fit ( At least not directly ) The needs mentioned above .


Solution 1 :useImperativeHandle

ColorLight.js

import React, {forwardRef, useCallback, useImperativeHandle, useState} from 'react';

const style = {
  height: "50px",
  width: "50px",
  borderRadius: "50%",
};

function getRandomColor({
  var letters = '0123456789ABCDEF';
  var color = '#';
  for (var i = 0; i < 6; i++) {
    color += letters[Math.floor(Math.random() * 16)];
  }
  return color;
}


const ColorLight = (props, ref) => {
  const [color, setColor] = useState("#bbb");
  const setRandomColor = useCallback(() => {
    setColor(getRandomColor())
  }, [setColor])

  //  The most critical code :  The second parameter returns an object  ——  Definition   The parent component can call this component   Methods 
  useImperativeHandle(ref, () => ({   
    changeColor: setRandomColor
  }), [setRandomColor])

  return (
      <div style={{...style, backgroundColor: color}}
           onClick={setRandomColor}/>
  );
};

export default forwardRef(ColorLight);  //  Don't forget  forwardRef

The core part of the above code is the use of useImperativeHandle , The second parameter returns an object —— Defined The parent component can call this component Methods , namely ColorLight Exposed a way called changeColor, The corresponding internal implementation is setRandomColor

Besides , Remember to call forwardRef , Otherwise, the parent component cannot ref To this component .



The advantages of this scheme :

  1. ref={ref} It's written in the same way as above DOM Element references are the same , Better consistency
  2. Use React Native methods and Hook, The code is relatively simple



Option two :Props Callback + useEffect + useRef


Counter.js

import React, {useCallback, useRef, useState} from 'react';
import ColorLight from './ColorLight';
import ColorLight2 from './ColorLight2';

const Counter3 = () => {
  const [counter, setCounter] = useState(0);
  const ref = useRef(); 

  const increaseHandler = () => {
    setCounter(counter + 1);
    ref.current.changeColor();
  }

  // ref current  Of  setter  Method , As  props  Pass to   Child components  ColorLight2
  const setChangeColorHandler = useCallback((handler) => {
    ref.current = {
      changeColor: handler
    }
  }, [ref]); // 

  return (
      <div>
        <div>
          Counter: <span>{counter}</span>
        </div>
        <br/>
        <button onClick={increaseHandler}>Click To Increase Counter and Focus Input</button>
        <br/>
        <br/>
        <ColorLight2 setChangeColorHandler={setChangeColorHandler}/> // props  In the form of  setChangeColorHandler
      </div>
  );
};

export default Counter3;


ColorLight2.js

import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useState} from 'react';

const style = {
  height"50px",
  width"50px",
  borderRadius"50%",
};

function getRandomColor({
  var letters = '0123456789ABCDEF';
  var color = '#';
  for (var i = 0; i < 6; i++) {
    color += letters[Math.floor(Math.random() * 16)];
  }
  return color;
}


const ColorLight2 = (props) => {
  const [color, setColor] = useState("#bbb");
  const setRandomColor = useCallback(() => {
    setColor(getRandomColor())
  }, [setColor])


  //  utilize  useEffect  After the rendering, it is called  props.setChangeColorHandler 
  useEffect(() => { 
    props.setChangeColorHandler(setRandomColor)
  }, [props.setChangeColorHandler, setRandomColor]);

  return (
      <div style={{...style, backgroundColor: color}}
           onClick={setRandomColor}/>
  );
};

export default ColorLight2;


The key difference from plan one is —— We use our own logic code to give ref.current Set the value :

adopt props In the shape of setChangeColorHandler Pass to subcomponent ( namely ColorLight2 ), ColorLight2 utilize useEffect After the rendering, it is called props.setChangeColorHandler The internal method setRandomColor Pass back to the parent component


The advantages of scheme two :

  1. Using the more common hook (useRef, useEffect) as well as props How to pass a callback function , Compared to the remote useImperativeHandle hook It's easier to understand

shortcoming :

  1. A little bit more code , The code logic is a little more complicated , So I don't recommend ( Personal view , Opinions differ )



introspection : Is this component structure reasonable ?

Unfortunately , Most component structure designs that require the parent component to call child component methods are usually unreasonable .

Because it's close to Command programming (Imperative Programming) instead of React The recommended Declarative programming (Declarative Programming)

Command programming : Use code to guide the program step by step in detail

Declarative programming : Describe the nature of the program objectives , Focus on the goal , Not the process

for instance :

Material-UI Dialog Dialog components   Whether to open is from props Medium open Attribute to control Of , Instead of exposing a open() Method and close() Methods to control .

The former is declarative , The latter is imperative .

Is there anything wrong with the imperative ?

No , There's nothing wrong with imperative programming , Like the vast majority of C Programs are imperative .

It's just React In the design and development of , What we advocate is declarative programming . stay React In the component , We should be Focus on state and props On , and View What does it look like , All we need to do is JSX It's related to state/props That's a good relationship .

Review the examples at the beginning of this article :

Imperative code :

  const counter = document.querySelector("#counter");

  document.querySelector("#increase-button").addEventListener("click"e => {
    counter.innerText = (parseInt(counter.innerText) + 1).toString();
  });

Button click callback function code directly manipulated DOM node , take counter Of innerText Set to new value


and React Code

const Counter = () => {
  const [counter, setCounter] = useState(0);
  const increaseHandler = () => {
    setCounter(counter + 1);
  }

  return (
      <div>
        <div>
          Counter: <span>{counter}</span>
        </div>
        <button onClick={increaseHandler}>Click To Increase Counter and Focus Input</button>
      </div>
  );
};

In the returned JSX in , The code declares span The corresponding element in this component is counter This state Value .counter state What value has it become ,React Just help us automatically in DOM What values are rendered on the screen —— We don't have to manipulate DOM node

That's in React What's the advantage of our own code conforming to declarative programming in development ?
For example, improve the readability of code 、 Reusability , decoupling , Convenient refactoring, etc , in addition , I personally think the most important thing is —— Fit React The design of the , enjoy React Design brings benefits while avoiding unnecessary pits (when in rome do as the romans do )


It's a little far away ( I have a chance to write an article about React The idea of declarative programming )~

Some readers will ask :“ Whatever React What do you recommend , The product manager is still pushing for progress ! I just want to implement my business logic !”

Don't worry ! Let's see , How to make the code implement business logic and conform to declarative programming .


Option three ( recommend ): Status up

Counter.js

const Counter = () => {
  const [counter, setCounter] = useState(0);
  const [color, setColor] = useState("#bbb");
  const increaseHandler = () => {
    setCounter(counter + 1);
    setColor(getRandomColor())
  }

  return (
      <div>
        <div>
          Counter: <span>{counter}</span>
        </div>
        <br/>
        <button onClick={increaseHandler}>Click To Increase Counter and Focus Input</button>
        <br/>
        <br/>
        <ColorLight color={color}/>
      </div>
  );
};

export default Counter;

ColorLight.js

const ColorLight = (props) => {
  const {color} = props;

  return (
      <div style={{...style, backgroundColor: color}}/>
  );
};

export default ColorLight;

The core of this refactoring is —— We put ColorLight Of color state Deleted , It's about receiving a color props attribute ( Status up ). thus ColorLight It becomes a very simple pure component , The logic of how to change colors is promoted to the parent component .

This design refactoring gets rid of the need for the parent component to call the child component method , It saves a lot of trouble .


Of course , There are other ways to improve your state , If the state is stored in Redux in , It's also possible in some scenarios , There is no example here .



summary

When React When components are not designed properly , You encounter the need to call subcomponent methods , In this case , Instead of writing imperative code, we should try to refactor the component design , Complete the requirements by raising the status, etc .

Of course , In special circumstances ( Such as deadline Will be to , Component refactoring is expensive ), have access to React refs, useImperativeHandle Implementation of imperative code completion requirements .




If you think the article is well written , It's helpful and enlightening for you , Welcome to thumb up 、 like 、 Collect Sanlian , Encourage me to write more articles !






Reference link :

https://stackoverflow.com/questions/37949981/call-child-method-from-parent

https://codeburst.io/declarative-vs-imperative-programming-a8a7c93d9ad2

https://reactjs.org/docs/forwarding-refs.html

https://reactjs.org/docs/refs-and-the-dom.html


版权声明
本文为[osc_ oz0d1seh]所创,转载请带上原文链接,感谢
https://cdmana.com/2020/12/20201224141846072G.html

Scroll to Top