React Hook:将数据从子组件发送到父组件

React Hook : Send data from child to parent component

我正在寻找将数据从子组件传递到父组件的最简单的解决方案。

我听说过使用 Context,通过属性传递或更新 props,但我不知道哪个是最好的解决方案。

我正在构建一个管理界面,其中的 PageComponent 包含一个带有 table 的 ChildComponent,我可以在其中 select 多行。我想将我在 ChildComponent 中 select 编辑的行数发送给我的父 PageComponent。

类似的东西:

页面组件:

<div className="App">
  <EnhancedTable />         
  <h2>count 0</h2>
  (count should be updated from child)
</div>

子组件:

 const EnhancedTable = () => {
     const [count, setCount] = useState(0);
     return (
       <button onClick={() => setCount(count + 1)}>
          Click me {count}
       </button>
     )
  };

我确信这是一件非常简单的事情,我不想为此使用 redux。

这些情况的常用技术是 lift the state up 到所有需要使用状态的组件的第一个共同祖先(即本例中的 PageComponent )并向下传递状态和状态改变函数作为 props 传递给子组件。

例子

const { useState } = React;

function PageComponent() {
  const [count, setCount] = useState(0);
  const increment = () => {
    setCount(count + 1)
  }

  return (
    <div className="App">
      <ChildComponent onClick={increment} count={count} />         
      <h2>count {count}</h2>
      (count should be updated from child)
    </div>
  );
}

const ChildComponent = ({ onClick, count }) => {
  return (
    <button onClick={onClick}>
       Click me {count}
    </button>
  )
};

ReactDOM.render(<PageComponent />, document.getElementById("root"));
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="root"></div>

您可以在父组件中创建一个方法,将其传递给子组件,并在每次子状态发生变化时从 props 中调用它,将状态保持在子组件中。

    const EnhancedTable = ({ parentCallback }) => {
        const [count, setCount] = useState(0);
        
        return (
            <button onClick={() => {
                const newValue = count + 1;
                setCount(newValue);
                parentCallback(newValue);
            }}>
                 Click me {count}
            </button>
        )
    };

    class PageComponent extends React.Component { 
        callback = (count) => {
            // do something with value in parent component, like save to state
        }

        render() {
            return (
                <div className="App">
                    <EnhancedTable parentCallback={this.callback} />         
                    <h2>count 0</h2>
                    (count should be updated from child)
                </div>
            )
        }
    }

为了让事情变得超级简单,您实际上可以将状态 setter 共享给子级,现在他们可以设置其父级的状态。

示例: 假设有以下4个组件,

function App() {
  return (
    <div className="App">
      <GrandParent />
    </div>
  );
}

const GrandParent = () => {
  const [name, setName] = useState("i'm Grand Parent");
  return (
    <>
      <div>{name}</div>
      <Parent setName={setName} />
    </>
  );
};

const Parent = params => {
  return (
    <>
      <button onClick={() => params.setName("i'm from Parent")}>
        from Parent
      </button>
      <Child setName={params.setName} />
    </>
  );
};

const Child = params => {
  return (
    <>
      <button onClick={() => params.setName("i'm from Child")}>
        from Child
      </button>
    </>
  );
};

因此祖父组件具有实际状态,通过将 setter 方法 (setName) 共享给父子组件,他们可以获得更改祖父组件状态的权限。

您可以在下面的沙箱中找到工作代码, https://codesandbox.io/embed/async-fire-kl197

我不得不处理类似的问题,并找到了另一种方法,使用对象来引用不同函数之间的状态,并且在同一个文件中。

import React, { useState } from "react";

let myState = {};

const GrandParent = () => {
  const [name, setName] = useState("i'm Grand Parent");
  myState.name=name;
  myState.setName=setName;
  return (
    <>
      <div>{name}</div>
      <Parent />
    </>
  );
};
export default GrandParent;

const Parent = () => {
  return (
    <>
      <button onClick={() => myState.setName("i'm from Parent")}>
        from Parent
      </button>
      <Child />
    </>
  );
};

const Child = () => {
  return (
    <>
      <button onClick={() => myState.setName("i'm from Child")}>
        from Child
      </button>
    </>
  );
};

我必须在 键入脚本 中执行此操作。面向对象方面需要开发者在继承parent后在接口中添加这个回调方法作为一个字段,这个prop的类型为Function。我发现这很酷!

这是我们如何将状态直接传递给父级的另一个示例。

我修改了 react-select 库中的一个组件示例,它是一个 CreatableSelect 组件。该组件最初是作为基于 class 的组件开发的,我将其变成了功能组件并更改了状态操作算法。

import React, {KeyboardEventHandler} from 'react';
import CreatableSelect from 'react-select/creatable';
import { ActionMeta, OnChangeValue } from 'react-select';

const MultiSelectTextInput = (props) => {
    const components = {
        DropdownIndicator: null,
    };

    interface Option {
        readonly label: string;
        readonly value: string;
    }

    const createOption = (label: string) => ({
        label,
        value: label,
    });

    const handleChange = (value: OnChangeValue<Option, true>, actionMeta: ActionMeta<Option>) => {
        console.group('Value Changed');
        console.log(value);
        console.log(`action: ${actionMeta.action}`);
        console.groupEnd();
        props.setValue(value);
    };

    const handleInputChange = (inputValue: string) => {
        props.setInputValue(inputValue);
    };

    const handleKeyDown: KeyboardEventHandler<HTMLDivElement> = (event) => {
        if (!props.inputValue) return;

        switch (event.key) {
            case 'Enter':
            case 'Tab':
                console.group('Value Added');
                console.log(props.value);
                console.groupEnd();
                props.setInputValue('');
                props.setValue([...props.value, createOption(props.inputValue)])
                event.preventDefault();
        }
    };

    return (
        <CreatableSelect
            id={props.id}
            instanceId={props.id}
            className="w-100"
            components={components}
            inputValue={props.inputValue}
            isClearable
            isMulti
            menuIsOpen={false}
            onChange={handleChange}
            onInputChange={handleInputChange}
            onKeyDown={handleKeyDown}
            placeholder="Type something and press enter..."
            value={props.value}
        />
    );
};

export default MultiSelectTextInput;

我从我的下一个 js 项目的页面中这样调用它

import MultiSelectTextInput from "../components/Form/MultiSelect/MultiSelectTextInput";

const NcciLite = () => {
    const [value, setValue] = useState<any>([]);
    const [inputValue, setInputValue] = useState<any>('');

    return (
        <React.Fragment>
            ....
            <div className="d-inline-flex col-md-9">
                <MultiSelectTextInput
                    id="codes"
                    value={value}
                    setValue={setValue}
                    inputValue={inputValue}
                    setInputValue={setInputValue}
                />
            </div>
            ...
        </React.Fragment>
    );
};

如图所示,该组件修改了调用它的页面(父页面)的状态。

如果我们有父 Class 组件和子功能组件,这就是我们访问子组件 useStates 挂钩值的方式:--

class parent extends Component() {
 
  constructor(props){
    super(props)
    this.ChildComponentRef = React.createRef()   
  }
  
  render(){
    console.log(' check child stateValue: ', 
    this.ChildComponentRef.current.info);
    return (<> <ChildComponent ref={this.ChildComponentRef} /> </>)
 }
}

我们将使用

创建的子组件

React.forwardRef((props, ref) => (<></>))

。和

useImperativeHandle(ref, createHandle, [deps])

自定义暴露给父组件的实例值

const childComponent = React.forwardRef((props, ref) => {
   const [info, setInfo] = useState("")

   useEffect(() => {
      axios.get("someUrl").then((data)=>setInfo(data))
   })

  useImperativeHandle(ref, () => {
      return {
          info: info
      }
  })
  return (<> <h2> Child Component <h2> </>)
 })