在反应中调用子级函数?

时间:2019-03-27 20:34:17

标签: javascript reactjs

我有一个包含多个Views的应用。每个View具有一定数量的映射子Entities。这些实体中的每一个都有一个状态,表示是否折叠/隐藏,称为this.state.show。每个Entity都有一个切换show状态的按钮。 一切正常。

我想做的是在View上添加一个按钮,该按钮可同时显示或隐藏所有Entities。我以为这很简单,但是有几个复杂的因素:

  • 我最终将在应用程序中拥有多个Views,每个应用程序以不同的方式显示相同的数据,因此将show存储在提升的道具中并不是什么选择。我也不想在文件数据中存储UI状态,因为这使我误入歧途。
  • 当状态作为状态而不是Entity的道具直接存储在View上时,它使我更容易理解发生了什么
  • React文档(以及在线上的每个人)都说,您不应该基于更改的道具的状态,并且Entities的数量会经常更改。这就是为什么我不想将孩子的状态存储在父级中的原因,因为如果我这样做了并且实体发生了变化,那么我就必须以某种方式将道具的更改转换为状态的更改,强烈建议不要这样做。
  • 如果我要使用事件,那么这些事件可能会从一个View溢出到另一个事件。

在这里追求正确的策略是什么?我在想“反应”不够吗?

View.jsx

import React, { Component } from 'react'
import Entity from './Entity.jsx'
import Filter from './Filter.jsx'

class View extends Component {
    render() {
        return (
            <div className="view main">
                {this.props.entities ? this.props.entities.map(
                    (e, i) => (
                        <Entity
                            entity={e}
                            propertyTypes={this.props.propertyTypes}
                            key={i}
                            index={i}
                            taglists={this.props.taglists}
                            ee={this.props.ee}
                        />
                    )
                ) : ""}
            </div>
        )
    }
}

export default View

Entity.jsx

import React, { Component } from 'react'
import Property from './Property.jsx'
import Event from './Event.jsx'
import Relationship from './Relationship.jsx'

class Entity extends Component {
    constructor (props) {
        super(props);
        this.state = {
            show: true
        }
        this.showHide = this.showHide.bind(this)
    }

    showHide() {
        this.setState({
            show: !this.state.show
        })
    }

    render() {
        return (
            <div className="entity" id={this.props.entity.name}>
                <div className="entity-header">
                    <h2>
                        <input
                            type="text"
                            value={this.props.entity.name}
                            onChange={this.emit}
                            data-original={this.props.entity.name}
                            data-index={this.props.index}
                            data-event="rename"
                        ></input>
                    </h2>
                    <button
                        className="entity-collapse"
                        data-index={this.props.index}
                        onClick={this.showHide}
                    >{this.state.show ? "-" : "+"}</button>
                </div>
                <div className={this.state.show ? "entity-content" : "entity-content hidden"}>
                    ...
                    removed for clarity
                    ...
                </div>
            </div>
        )
    }
}

export default Entity

2 个答案:

答案 0 :(得分:2)

您的状态应仅在父组件中,而不能在任何子组件中。然后,您会将自己的功能和状态作为道具传递给孩子。

答案 1 :(得分:0)

下面是在高阶组件中存储状态的示例。

如果您看一下代码,则只有一个地方存储状态,即View中的位置,这被称为单一事实来源,这使得React中的编码变得更加简单。

例如,如果您单击all复选框,将全部选中。而且,如果您逐一检查,allnone也会保持同步。

您当然可以将其更改为所需的套件,我在这里使用了复选框,因为很容易看到状态更改起作用。

对于较大的应用程序,真理的单一来源方法变得更加重要,最好避免在React中存储状态。这就是Redux众所周知的,就像我在评论中提到的那样,Redux不是我个人使用的东西,而是使用代理滚动了我自己的真相状态管理源,如果我有时间的话,我可以使用它创建一个NPM模块,因为我确保其他用户会发现它有用。

ps ,.我在示例中使用了React Hooks,因为我相信这是React的未来,但是更改使用类将是微不足道的。

const {useState, cloneElement} = React;


function MultiCheck(props) {
  const {checkallto, list, setList} = props;
  const checked = !list.some(f => f !== checkallto);
  return <div>
    <input
      onClick={() => {
        setList(new Array(list.length).fill(checkallto));
      }}
      onChange={() => {}}
      checked={checked} 
      type="checkbox"/>
    {props.children}
  </div>;
}


function View(props) {
  const [list, setList] = useState(new Array(props.children.length).fill(false));
 
  function setEntityChecked(ix) {
    return (check) => {
      const newlist = [...list];
      newlist[ix] = check;
      setList(newlist);
    };
  }
  
  const children = props.children.map((
    f, ix) => cloneElement(f, {key:ix, checked: list[ix],
      setChecked: setEntityChecked(ix)}));
  return <div>
    <MultiCheck checkallto={true} list={list} setList={setList}>
      All
    </MultiCheck>
    <MultiCheck checkallto={false} list={list} setList={setList}>
      None
    </MultiCheck>
    <hr/>
    {children}
  </div>;
}

function Entity(props) {
  const {checked, setChecked} = props;
  return <div><input
    checked={checked}
    onChange={()=>{}}
    onClick={() => setChecked(!checked)}
    type="checkbox"/>{props.children}</div>;
}


ReactDOM.render(<React.Fragment>
  <View>
    <Entity>One</Entity>
    <Entity>Two</Entity>
    <Entity>Two</Entity>
  </View>
</React.Fragment>, document.querySelector('#mount'));
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="mount"></div>