ReactJS:子组件内父级的setState

ReactJS: setState on parent inside child component

从子组件对父组件执行 setState 的推荐模式是什么。

var Todos = React.createClass({
  getInitialState: function() {
    return {
      todos: [
        "I am done",
        "I am not done"
      ]
    }
  },

  render: function() {
    var todos = this.state.todos.map(function(todo) {
      return <div>{todo}</div>;
    });

    return <div>
      <h3>Todo(s)</h3>
      {todos}
      <TodoForm />
    </div>;
  }
});

var TodoForm = React.createClass({
  getInitialState: function() {
    return {
      todoInput: ""
    }
  },

  handleOnChange: function(e) {
    e.preventDefault();
    this.setState({todoInput: e.target.value});
  },

  handleClick: function(e) {
    e.preventDefault();
    //add the new todo item
  },

  render: function() {
    return <div>
      <br />
      <input type="text" value={this.state.todoInput} onChange={this.handleOnChange} />
      <button onClick={this.handleClick}>Add Todo</button>
    </div>;
  }
});

React.render(<Todos />, document.body)

我有一个待办事项数组,它们在父级状态下维护。 我想从 TodoFormhandleClick 组件访问父级的状态并添加一个新的待办事项。 我的想法是在父级上做一个 setState,它将渲染新添加的待办事项。

您可以在父组件中创建一个 addTodo 函数,将其绑定到该上下文,将其传递给子组件并从那里调用它。

// in Todos
addTodo: function(newTodo) {
    // add todo
}

然后,在 Todos.render,你会做

<TodoForm addToDo={this.addTodo.bind(this)} />

在 TodoForm 中使用

调用它
this.props.addToDo(newTodo);

在您的父组件中,您可以创建一个类似于 addTodoItem 的函数,它将执行所需的 setState,然后将该函数作为 props 传递给子组件。

var Todos = React.createClass({

  ...

  addTodoItem: function(todoItem) {
    this.setState(({ todos }) => ({ todos: { ...todos, todoItem } }));
  },

  render: function() {

    ...

    return <div>
      <h3>Todo(s)</h3>
      {todos}
      <TodoForm addTodoItem={this.addTodoItem} />
    </div>
  }
});

var TodoForm = React.createClass({
  handleClick: function(e) {
    e.preventDefault();
    this.props.addTodoItem(this.state.todoInput);
    this.setState({todoInput: ""});
  },

  ...

});

您可以在 TodoForm 的 handleClick 中调用 addTodoItem。这将在父级上执行 setState 以呈现新添加的待办事项。希望你明白了。

Fiddle here.

我找到了以下有效且简单的解决方案来将参数从子组件传递到父组件:

//ChildExt component
class ChildExt extends React.Component {
    render() {
        var handleForUpdate =   this.props.handleForUpdate;
        return (<div><button onClick={() => handleForUpdate('someNewVar')}>Push me</button></div>
        )
    }
}

//Parent component
class ParentExt extends React.Component {   
    constructor(props) {
        super(props);
        var handleForUpdate = this.handleForUpdate.bind(this);
    }
    handleForUpdate(someArg){
            alert('We pass argument from Child to Parent: \n' + someArg);   
    }

    render() {
        var handleForUpdate =   this.handleForUpdate;    
        return (<div>
                    <ChildExt handleForUpdate = {handleForUpdate.bind(this)} /></div>)
    }
}

if(document.querySelector("#demo")){
    ReactDOM.render(
        <ParentExt />,
        document.querySelector("#demo")
    );
}

Look at JSFIDDLE

这些基本上都是正确的,我只是想我会指出新的(差不多)官方 React 文档,它基本上推荐:-

There should be a single “source of truth” for any data that changes in a React application. Usually, the state is first added to the component that needs it for rendering. Then, if other components also need it, you can lift it up to their closest common ancestor. Instead of trying to sync the state between different components, you should rely on the top-down data flow.

参见https://reactjs.org/docs/lifting-state-up.html。该页面还通过示例工作。

parentSetState={(obj) => { this.setState(obj) }}

对于那些使用 React Hook 维护状态的人 useState,我采纳了上述建议,在下面制作了一个演示滑块应用程序。在演示应用程序中,子滑块组件保持父组件的状态。

该演示还使用了 useEffect 挂钩。 (更重要的是,useRef 钩子)

import React, { useState, useEffect, useCallback, useRef } from "react";

//the parent react component
function Parent() {

  // the parentState will be set by its child slider component
  const [parentState, setParentState] = useState(0);

  // make wrapper function to give child
  const wrapperSetParentState = useCallback(val => {
    setParentState(val);
  }, [setParentState]);

  return (
    <div style={{ margin: 30 }}>
      <Child
        parentState={parentState}
        parentStateSetter={wrapperSetParentState}
      />
      <div>Parent State: {parentState}</div>
    </div>
  );
};

//the child react component
function Child({parentStateSetter}) {
  const childRef = useRef();
  const [childState, setChildState] = useState(0);

  useEffect(() => {
    parentStateSetter(childState);
  }, [parentStateSetter, childState]);

  const onSliderChangeHandler = e => {
  //pass slider's event value to child's state
    setChildState(e.target.value);
  };

  return (
    <div>
      <input
        type="range"
        min="1"
        max="255"
        value={childState}
        ref={childRef}
        onChange={onSliderChangeHandler}
      ></input>
    </div>
  );
};

export default Parent;

如果您使用 class 组件作为父组件,将 setState 传递给子组件的一种非常简单的方法是在箭头函数中传递它。这是有效的,因为它设置了一个可以传递的提升环境:

class ClassComponent ... {

    modifyState = () =>{
        this.setState({...})   
    }
    render(){
          return <><ChildComponent parentStateModifier={modifyState} /></>
    }
}

对于在启用 TypeScriptArrow Functional Components 中使用 useState 的任何人,这里有一个简单的示例,说明如何传递 parent 的状态 setter child 的函数,并在适当的时间从 child-

调用它来设置 parent 的状态

Parent分量:

import {useState} from "react";
const ParentComponent = () => {
  const [hasParentStateChange, setHasParentStateChange] = useState<boolean>(false);

  return (
      <div>
        Parent's view: {String(hasParentStateChange)}
        <ChildComponent
            hasParentStateChange={hasParentStateChange}
            setHasParentStateChange={setHasParentStateChange} // <---- Passing parent's state setter over
        />
      </div>
  );
}

Child分量:

interface PropsDefinition {
  hasParentStateChange: boolean;
  setHasParentStateChange(data: boolean): void;
}

const ChildComponent = (props: PropsDefinition) => {
  return (
      <div>
        <div>
          Child's view: {String(props.hasParentStateChange)}
        </div>
        <button
            onClick={() => props.setHasParentStateChange(!props.hasParentStateChange)} // <---- Invoking parent's state setter
        >
            Toggle parent state from child
        </button>
      </div>
  );
}