[reactjs] 양식 요소 상태를 형제 / 부모 요소에 전달하는 올바른 방법은 무엇입니까?

  • 두 개의 자식 클래스 C1과 C2를 렌더링하는 React 클래스 P가 있다고 가정합니다.
  • C1은 입력 필드를 포함합니다. 이 입력 필드를 Foo라고합니다.
  • 저의 목표는 C2가 Foo의 변화에 ​​반응하도록하는 것입니다.

나는 두 가지 해결책을 생각해 냈지만 어느 것도 옳지 않다고 생각합니다.

첫 번째 해결책 :

  1. P에게 상태를 할당하십시오 state.input.
  2. onChange이벤트를 가져 와서 설정 하는 함수를 P로 만듭니다 state.input.
  3. 이것을 onChangeC1에 a props로 전달하고 C1 이 Foo에 바인딩 this.props.onChange되도록하십시오 onChange.

작동합니다. Foo 값이 변경 될 때마다 setStateP에서 a 를 트리거 하므로 P는 입력을 C2로 전달합니다.

그러나 같은 이유로 꽤 옳지 않습니다. 자식 요소에서 부모 요소의 상태를 설정하고 있습니다. 이것은 단일 방향 데이터 흐름 인 React의 디자인 원칙을 배신하는 것 같습니다.
이것이 내가 해야하는 방법입니까, 아니면 더 자연스러운 반응이 있습니까?

두 번째 해결책 :

F를 F에 넣으십시오.

그러나 이것이 앱을 구조화 할 때 따라야 할 디자인 원칙 render입니까? 모든 폼 요소를 최상위 수준 클래스 에 넣을 까요?

나는 C1의 큰 렌더링이있는 경우 내 예제처럼, 정말 전체를 넣어하지 않으 render에 C1의를 renderC1 양식 요소가해서 P의.

어떻게해야합니까?



답변

그래서, 내가 당신을 올바르게 이해하고 있다면, 첫 번째 해결책은 루트 구성 요소의 상태를 유지하고 있다고 제안하고 있습니까? 나는 React의 제작자를 위해 말할 수는 없지만 일반적으로 이것이 적절한 해결책이라고 생각합니다.

상태를 유지하는 것은 React가 만들어진 이유 중 하나입니다 (적어도 생각합니다). 상호 의존적 인 움직이는 부분이 많은 동적 UI를 처리하기 위해 자체 상태 패턴 클라이언트 측을 구현 한 경우 React를 좋아할 것입니다. 이는 상태 관리 문제를 많이 완화하기 때문입니다.

계층 구조에서 상태를 계속 유지하고 이벤트를 통해 업데이트하면 데이터 흐름은 여전히 ​​거의 단방향입니다. 루트 구성 요소의 이벤트에 응답하고 양방향 바인딩을 통해 실제로 데이터를 가져 오는 것은 아닙니다. 당신은 루트 컴포넌트에게 “여기서 뭔가 일어 났고, 값을 확인하십시오”라고 말하거나, 상태를 업데이트하기 위해 자식 컴포넌트의 일부 데이터 상태를 전달하고 있습니다. C1에서 상태를 변경했으며 C2가이를 인식하기를 원하므로 루트 구성 요소의 상태를 업데이트하고 다시 렌더링하면 C2의 prop은 이제 상태가 루트 구성 요소에서 업데이트되고 전달 된 이후 동기화됩니다. .

class Example extends React.Component {
  constructor (props) {
    super(props)
    this.state = { data: 'test' }
  }
  render () {
    return (
      <div>
        <C1 onUpdate={this.onUpdate.bind(this)}/>
        <C2 data={this.state.data}/>
      </div>
    )
  }
  onUpdate (data) { this.setState({ data }) }
}

class C1 extends React.Component {
    render () {
      return (
        <div>
          <input type='text' ref='myInput'/>
          <input type='button' onClick={this.update.bind(this)} value='Update C2'/>
        </div>
      )
    }
    update () {
      this.props.onUpdate(this.refs.myInput.getDOMNode().value)
    }
})

class C2 extends React.Component {
    render () {
      return <div>{this.props.data}</div>
    }
})

ReactDOM.renderComponent(<Example/>, document.body)


답변

지금 React를 사용하여 앱을 빌드 한 후 반년 전에 물었던이 질문에 대한 의견을 나누고 싶습니다.

나는 당신이 읽을 것을 권장합니다

첫 번째 게시물은 React 앱을 구성하는 방법을 이해하는 데 매우 도움이됩니다.

플럭스는 React 앱을 이런 식으로 구성해야하는지에 대한 질문에 대답합니다 ( 어떻게 구성 하는가). 반응은 시스템의 50 %에 불과하며 Flux를 사용하면 전체 그림을보고 이들이 일관된 시스템을 구성하는 방법을 볼 수 있습니다.

질문으로 돌아 가기

첫 번째 해결책 은 데이터 가 여전히 단일 방향으로 진행 되므로 처리기 가 반대 방향으로 이동하게하는 것이 좋습니다.

그러나 처리기가 P에서 setState를 트리거하게하는지 여부는 상황에 따라 맞거나 틀릴 수 있습니다.

앱이 간단한 Markdown 변환기 인 경우 C1이 원시 입력이고 C2가 HTML 출력 인 경우 C1이 P에서 setState를 트리거하도록하는 것이 좋지만 일부는 이것이 권장되는 방법이 아니라고 주장 할 수 있습니다.

응용 프로그램이 할 일 목록 경우, C1 새로운 할 일을 만들기위한 입력되고, C2 HTML에서 할 일 목록, 당신은 아마 P보다 두 단계 위로 이동 핸들러에 원하는 -받는 dispatcher송출하는 store업데이트를 data store, 그런 다음 데이터를 P로 보내고 뷰를 채 웁니다. Flux 기사를 참조하십시오. 예제는 다음과 같습니다. Flux-TodoMVC

일반적으로 할 일 목록 예제에 설명 된 방식을 선호합니다. 앱의 상태가 적을수록 좋습니다.


답변

5 년 후 React Hooks가 도입되면서 useContext Hook를 사용하여 훨씬 더 우아한 방법을 사용할 수있게되었습니다.

전역 범위에서 컨텍스트를 정의하고 상위 구성 요소에서 변수, 객체 및 함수를 내 보낸 다음 제공된 컨텍스트에서 앱의 하위를 래핑하고 하위 구성 요소에 필요한 모든 것을 가져옵니다. 아래는 개념 증명입니다.

import React, { useState, useContext } from "react";
import ReactDOM from "react-dom";
import styles from "./styles.css";

// Create context container in a global scope so it can be visible by every component
const ContextContainer = React.createContext(null);

const initialAppState = {
  selected: "Nothing"
};

function App() {
  // The app has a state variable and update handler
  const [appState, updateAppState] = useState(initialAppState);

  return (
    <div>
      <h1>Passing state between components</h1>

      {/*
          This is a context provider. We wrap in it any children that might want to access
          App's variables.
          In 'value' you can pass as many objects, functions as you want.
           We wanna share appState and its handler with child components,
       */}
      <ContextContainer.Provider value={{ appState, updateAppState }}>
        {/* Here we load some child components */}
        <Book title="GoT" price="10" />
        <DebugNotice />
      </ContextContainer.Provider>
    </div>
  );
}

// Child component Book
function Book(props) {
  // Inside the child component you can import whatever the context provider allows.
  // Earlier we passed value={{ appState, updateAppState }}
  // In this child we need the appState and the update handler
  const { appState, updateAppState } = useContext(ContextContainer);

  function handleCommentChange(e) {
    //Here on button click we call updateAppState as we would normally do in the App
    // It adds/updates comment property with input value to the appState
    updateAppState({ ...appState, comment: e.target.value });
  }

  return (
    <div className="book">
      <h2>{props.title}</h2>
      <p>${props.price}</p>
      <input
        type="text"
        //Controlled Component. Value is reverse vound the value of the variable in state
        value={appState.comment}
        onChange={handleCommentChange}
      />
      <br />
      <button
        type="button"
        // Here on button click we call updateAppState as we would normally do in the app
        onClick={() => updateAppState({ ...appState, selected: props.title })}
      >
        Select This Book
      </button>
    </div>
  );
}

// Just another child component
function DebugNotice() {
  // Inside the child component you can import whatever the context provider allows.
  // Earlier we passed value={{ appState, updateAppState }}
  // but in this child we only need the appState to display its value
  const { appState } = useContext(ContextContainer);

  /* Here we pretty print the current state of the appState  */
  return (
    <div className="state">
      <h2>appState</h2>
      <pre>{JSON.stringify(appState, null, 2)}</pre>
    </div>
  );
}

const rootElement = document.body;
ReactDOM.render(<App />, rootElement);

Code Sandbox 편집기에서이 예제를 실행할 수 있습니다.

상황에 따라 전달 상태 편집


답변

제 용액으로 부모 구성 요소의 상태를 유지 한다 올바른 . 그러나 더 복잡한 문제의 경우 일부 상태 관리 라이브러리 에 대해 생각해야 하며 redux 는 react와 함께 사용되는 가장 인기있는 라이브러리 입니다.


답변

내가 쓰는 순간에 간단한 관용적 반응 솔루션에 대한 답변이 없다는 것에 놀랐습니다. 여기에 하나가 있습니다 (다른 사람들과 크기와 복잡성을 비교하십시오).

class P extends React.Component {
    state = { foo : "" };

    render(){
        const { foo } = this.state;

        return (
            <div>
                <C1 value={ foo } onChange={ x => this.setState({ foo : x })} />
                <C2 value={ foo } />
            </div>
        )
    }
}

const C1 = ({ value, onChange }) => (
    <input type="text"
           value={ value }
           onChange={ e => onChange( e.target.value ) } />
);

const C2 = ({ value }) => (
    <div>Reacting on value change: { value }</div>
);

자식 요소에서 부모 요소의 상태를 설정하고 있습니다. 이것은 단일 방향 데이터 흐름 인 React의 디자인 원칙을 배신하는 것 같습니다.

모든 제어input (반작용의 형태로 작업의 관용적 방법)의에 부모 상태를 업데이트 onChange콜백 여전히 배신 아무것도하지 않습니다.

예를 들어 C1 구성 요소를주의 깊게 살펴보십시오. C1내장 input구성 요소가 상태 변경을 처리하는 방법에 큰 차이가 있습니까? 아무것도 없기 때문에해서는 안됩니다. 상태를 높이고 값 / onChange 쌍을 전달하는 것은 원시 React에 관용적입니다. 일부 답변에서 알 수 있듯이 심판의 사용은 아닙니다.


답변

예제를 사용하는 최신 답변 React.useState

부모 구성 요소의 상태를 유지하는 것이 좋습니다. 부모는 두 개의 자식 구성 요소를 관리하기 때문에 액세스 권한이 있어야합니다. Redux에서 관리하는 것과 같이 전역 상태로 옮기는 것은 소프트웨어 엔지니어링에서 전역 변수가 일반적으로 로컬보다 나쁜 이유와 같은 이유로 권장 되지 않습니다 .

상태가 부모 구성 요소에있을 때 부모가 자식 valueonChange처리기를 props (때로는 값 링크 또는 상태 링크 패턴 이라고 함)에 제공하면 자식은이를 변경할 수 있습니다 . 후크로 수행하는 방법은 다음과 같습니다.


function Parent() {
    var [state, setState] = React.useState('initial input value');
    return <>
        <Child1 value={state} onChange={(v) => setState(v)} />
        <Child2 value={state}>
    </>
}

function Child1(props) {
    return <input
        value={props.value}
        onChange={e => props.onChange(e.target.value)}
    />
}

function Child2(props) {
    return <p>Content of the state {props.value}</p>
}

전체 부모 구성 요소는 자식의 입력 변경시 다시 렌더링되며, 부모 구성 요소가 작거나 다시 렌더링하기에 빠르면 문제가되지 않을 수 있습니다. 부모 구성 요소의 다시 렌더링 성능은 여전히 ​​일반적인 경우에 문제가 될 수 있습니다 (예 : 큰 양식). 이것은 귀하의 경우 해결 된 문제입니다 (아래 참조).

주 링크 패턴부모를 가지지 다시 렌더링 과 같은 제 3 자 라이브러리를 사용하여 구현하기가 쉽다 Hookstate – 슈퍼 React.useState당신의 일을 포함하여 사용 사례 커버 다양한에 있습니다. (면책 조항 : 나는 프로젝트의 저자입니다).

다음은 Hookstate에서 어떻게 보이는지입니다. Child1입력을 변경하고 Child2반응합니다. Parent에만 상태를 유지하지만 상태 변경에 다시 렌더링하지 않습니다 Child1Child2것입니다.

import { useStateLink } from '@hookstate/core';

function Parent() {
    var state = useStateLink('initial input value');
    return <>
        <Child1 state={state} />
        <Child2 state={state}>
    </>
}

function Child1(props) {
    // to avoid parent re-render use local state,
    // could use `props.state` instead of `state` below instead
    var state = useStateLink(props.state)
    return <input
        value={state.get()}
        onChange={e => state.set(e.target.value)}
    />
}

function Child2(props) {
    // to avoid parent re-render use local state,
    // could use `props.state` instead of `state` below instead
    var state = useStateLink(props.state)
    return <p>Content of the state {state.get()}</p>
}

추신 : 여기 에는 깊게 중첩 된 데이터, 상태 유효성 검사, setState후크가있는 전역 상태 등을 포함하여 유사하고 복잡한 시나리오를 다루는 더 많은 예제 가 있습니다 . Hookstate 및 위에서 설명한 기술을 사용하는 완전한 샘플 응용 프로그램이 온라인 에 있습니다.


답변

Redux와 ReactRedux 라이브러리를 배워야합니다. 상태와 소품을 한 상점에서 구성하고 나중에 컴포넌트에서 액세스 할 수 있습니다.