Reactjs : 동적 자식 구성 요소 상태 또는 부모에서 소품을 수정하는 방법은 무엇입니까?


90

나는 본질적으로 반응하는 탭을 만들려고 노력하고 있지만 몇 가지 문제가 있습니다.

여기에 파일이 있습니다 page.jsx

<RadioGroup>
    <Button title="A" />
    <Button title="B" />
</RadioGroup>

버튼 A를 클릭하면 RadioGroup 구성 요소가 버튼 B의 선택을 취소해야합니다 .

"선택됨"은 상태 또는 속성의 className을 의미합니다.

여기 있습니다 RadioGroup.jsx:

module.exports = React.createClass({

    onChange: function( e ) {
        // How to modify children properties here???
    },

    render: function() {
        return (<div onChange={this.onChange}>
            {this.props.children}
        </div>);
    }

});

의 소스는 Button.jsx중요하지 않습니다. 기본 DOM onChange이벤트 를 트리거하는 일반 HTML 라디오 버튼이 있습니다.

예상되는 흐름은 다음과 같습니다.

  • 버튼 "A"를 클릭하십시오
  • 버튼 "A"는 RadioGroup으로 버블 링되는 네이티브 DOM 이벤트 인 onChange를 트리거합니다.
  • RadioGroup onChange 리스너가 호출됩니다.
  • RadioGroup은 버튼 B의 선택을 취소해야합니다 . 제 질문입니다.

내가 직면 한 주요 문제는 다음과 같습니다. s를으로 이동할 수 없습니다<Button>RadioGroup . 구조가 자식이 임의적 이기 때문 입니다. 즉, 마크 업은

<RadioGroup>
    <Button title="A" />
    <Button title="B" />
</RadioGroup>

또는

<RadioGroup>
    <OtherThing title="A" />
    <OtherThing title="B" />
</RadioGroup>

나는 몇 가지 시도했습니다.

시도 : 에서 RadioGroup의 onChange가 핸들러를 :

React.Children.forEach( this.props.children, function( child ) {

    // Set the selected state of each child to be if the underlying <input>
    // value matches the child's value

    child.setState({ selected: child.props.value === e.target.value });

});

문제:

Invalid access to component property "setState" on exports at the top
level. See react-warning-descriptors . Use a static method
instead: <exports />.type.setState(...)

시도 : 에서 RadioGroup의 onChange가 핸들러를 :

React.Children.forEach( this.props.children, function( child ) {

    child.props.selected = child.props.value === e.target.value;

});

문제 : 아무 일도 일어나지 않습니다. 심지어 제가 Button클래스에 componentWillReceiveProps메서드를 제공합니다.


시도 : 부모의 특정 상태를 자식에게 전달하려고했기 때문에 부모 상태를 업데이트하고 자식이 자동으로 응답하도록 할 수 있습니다. RadioGroup의 렌더링 기능에서 :

React.Children.forEach( this.props.children, function( item ) {
    this.transferPropsTo( item );
}, this);

문제:

Failed to make request: Error: Invariant Violation: exports: You can't call
transferPropsTo() on a component that you don't own, exports. This usually
means you are calling transferPropsTo() on a component passed in as props
or children.

나쁜 솔루션 # 1 : react-addons.js cloneWithProps 메서드를 사용하여 렌더링시 자식을 복제하여 RadioGroup속성을 전달할 수 있습니다.

나쁜 솔루션 # 2 : HTML / JSX에 대한 추상화를 구현하여 동적으로 속성을 전달할 수 있습니다.

<RadioGroup items=[
    { type: Button, title: 'A' },
    { type: Button, title: 'B' }
]; />

그런 다음 RadioGroup동적으로 이러한 버튼을 만듭니다.

이 질문 은 저에게 도움 되지 않습니다. 아이들이 무엇인지 모르고 렌더링해야하기 때문입니다.


아이들이 임의적 일 수 있다면, RadioGroup임의의 자식의 사건에 반응해야한다는 것을 어떻게 알 수 있을까요? 반드시 자녀에 대해 알아야합니다.
Ross Allen

1
일반적으로 소유하지 않은 컴포넌트의 속성을 수정하려면이를 사용하여 복제하고 React.addons.cloneWithProps원하는 새 소품을 전달합니다. props불변이므로 새 해시를 만들고 현재 소품과 병합하고 새 해시를 props자식 구성 요소의 새 인스턴스에 전달합니다 .
Ross Allen

답변:


42

왜 사용 cloneWithProps이 나쁜 해결책 이라고 말하는지 잘 모르겠지만 여기에 사용하는 실제 예제가 있습니다.

var Hello = React.createClass({
    render: function() {
        return <div>Hello {this.props.name}</div>;
    }
});

var App = React.createClass({
    render: function() {
        return (
            <Group ref="buttonGroup">
                <Button key={1} name="Component A"/>
                <Button key={2} name="Component B"/>
                <Button key={3} name="Component C"/>
            </Group>
        );
    }
});

var Group = React.createClass({
    getInitialState: function() {
        return {
            selectedItem: null
        };
    },

    selectItem: function(item) {
        this.setState({
            selectedItem: item
        });
    },

    render: function() {
        var selectedKey = (this.state.selectedItem && this.state.selectedItem.props.key) || null;
        var children = this.props.children.map(function(item, i) {
            var isSelected = item.props.key === selectedKey;
            return React.addons.cloneWithProps(item, {
                isSelected: isSelected,
                selectItem: this.selectItem,
                key: item.props.key
            });
        }, this);

        return (
            <div>
                <strong>Selected:</strong> {this.state.selectedItem ? this.state.selectedItem.props.name : 'None'}
                <hr/>
                {children}
            </div>
        );
    }

});

var Button = React.createClass({
    handleClick: function() {
        this.props.selectItem(this);
    },

    render: function() {
        var selected = this.props.isSelected;
        return (
            <div
                onClick={this.handleClick}
                className={selected ? "selected" : ""}
            >
                {this.props.name} ({this.props.key}) {selected ? "<---" : ""}
            </div>
        );
    }

});


React.renderComponent(<App />, document.body);

다음 은 실제로 작동 하는 jsFiddle 입니다.

편집 : 여기에 동적 탭 콘텐츠가있는 더 완전한 예가 있습니다. jsFiddle


15
참고 : cloneWithProps는 더 이상 사용되지 않습니다. 대신 React.cloneElement를 사용하십시오.
첸 츠 린

8
이것은 D3 / Jquery가 : not 선택기로 할 수있는 일을하기에는 믿을 수 없을 정도로 복잡합니다 this. React를 사용한 것 외에 실질적인 이점이 있습니까?
사례 별 학습 통계

참고로 이것은 "부모 상태에서 자식 상태를 업데이트하는 것"이 ​​아니라 자식 상태를 업데이트하기 위해 부모 상태를 자식으로 업데이트하는 것입니다. 여기에 동사의 차이가 있습니다. 이것이 사람들을 혼란스럽게 할 경우.
sksallaj

1
@Incodeveritas 당신이 옳습니다, 형제 관계, 부모-자식 및 자식-부모는 약간 복잡합니다. 이것은 일을하는 모듈 식 방법입니다. 그러나 JQuery는 작업을 완료하는 빠른 해킹이며 ReactJS는 오케스트레이션에 따라 작업을 유지합니다.
sksallaj

18

버튼은 상태 비 저장이어야합니다. 버튼의 속성을 명시 적으로 업데이트하는 대신 그룹의 자체 상태를 업데이트하고 다시 렌더링하면됩니다. Group의 render 메서드는 버튼을 렌더링 할 때 상태를 확인하고 "active"(또는 무언가)를 활성 버튼에만 전달해야합니다.


확장하려면 버튼 onClick 또는 기타 관련 메서드를 부모에서 설정해야합니다. 그래야 모든 작업이 부모로 다시 호출되어 버튼 자체가 아닌 상태를 설정합니다. 이런 식으로 버튼은 부모의 현재 상태를 상태 비 저장으로 표현한 것입니다.
David Mårtensson

6

내 것이 이상한 해결책 일 수도 있지만 관찰자 패턴을 사용하지 않는 이유는 무엇입니까?

RadioGroup.jsx

module.exports = React.createClass({
buttonSetters: [],
regSetter: function(v){
   buttonSetters.push(v);
},
handleChange: function(e) {
   // ...
   var name = e.target.name; //or name
   this.buttonSetters.forEach(function(v){
      if(v.name != name) v.setState(false);
   });
},
render: function() {
  return (
    <div>
      <Button title="A" regSetter={this.regSetter} onChange={handleChange}/>
      <Button title="B" regSetter={this.regSetter} onChange={handleChange} />
    </div>
  );
});

Button.jsx

module.exports = React.createClass({

    onChange: function( e ) {
        // How to modify children properties here???
    },
    componentDidMount: function() {
         this.props.regSetter({name:this.props.title,setState:this.setState});
    },
    onChange:function() {
         this.props.onChange();
    },
    render: function() {
        return (<div onChange={this.onChange}>
            <input element .../>
        </div>);
    }

});

다른 것이 필요할 수도 있지만이게 매우 강력하다는 것을 알았습니다.

다양한 작업에 대한 관찰자 등록 방법을 제공하는 외부 모델을 사용하는 것을 정말 선호합니다.


노드가 다른 노드에서 명시 적으로 setState를 호출하는 것은 나쁜 형식이 아닙니까? 보다 반응적인 방법은 RadioGroup의 일부 상태를 업데이트하여 필요에 따라 하위 항목의 소품을 업데이트 할 수있는 또 다른 렌더 패스를 트리거하는 것입니까?
qix

1
분명히? 아니요, 관찰자는 콜백을 호출하기 만하면 setState가됩니다.하지만 실제로 this.setState.bind (this)를 지정할 수 있으므로 자신에게 배타적으로 bindend됩니다. 나는 정직해야한다면 좋아, 나는 로컬 함수 / 메소드가 호출 setState를 정의하고 관찰자에 등록했다한다
다니엘 Cruciani에게

1
내가 잘못 읽었 Button.jsx습니까? 아니면 하나의 개체에 같은 이름을 가진 두 개의 필드가 있습니까?
JohnK

Button.jsx에는 다음이 포함됩니다. onChange() 하고onChange(e)
조쉬

첫 번째를 제거하면 질문에서 잘라내어 붙여 넣습니다 (첫 번째 질문). 그것은 요점이 아닙니다. 이것은 작동하는 코드도 아닙니다.
다니엘 Cruciani

0

부모와 자식 사이에서 중개자 역할을하는 개체를 만듭니다. 이 개체는 부모와 자식 모두에 함수 참조를 포함합니다. 그런 다음 객체는 부모에서 자식에게 소품으로 전달됩니다. 여기에 코드 예 :

https://stackoverflow.com/a/61674406/753632

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.