切换无状态反应组件数组的可见性

时间:2018-08-01 21:17:06

标签: javascript reactjs

我试图简单地映射从api返回的一些数据,并为返回的每个对象创建一个无状态组件。我希望能够单击任何组件来切换其其余数据的可见性。

我已经尝试了许多方法来做到这一点并不断撞墙,我也一直在寻找堆栈溢出的问题,而且似乎找不到答案。

我已经通过使它们成为单独的类组件来使其工作,但是,似乎只有很多切换功能才需要很多代码。

在此先感谢您的帮助或见解,以下是我的简要介绍。

为澄清起见,这是我学习使用react和外部api的简单应用,它未使用redux。

以类组件的状态获取用户

class PersonList extends Component {
  constructor(props) {
    super(props);
    this.state = {
      resource: []
    };
  }

  async componentDidMount() {
    let fetchedData = await API_Call("people");
    this.setState({ resource: fetchedData.results });
    while (fetchedData.next) {
      let req = await fetch(fetchedData.next);
      fetchedData = await req.json();
      this.setState({
        resource: [...this.state.resource, ...fetchedData.results]
      });
    }
  }
}

然后在结果上映射并为每个结果呈现一个组件

render() {
  const mappedPeople = this.state.resource.map((person, i) => (
    <Person key={i} {...person} />
  ));
  return <div>{mappedPeople}</div>;
}

我可以在其中使每个人组件成为无状态组件,并且可以单击它并显示其余数据吗?这是我目前所拥有的。

class Person extends Component {
  constructor(props) {
    super(props);
    this.state = {
      visibility: false
    };
  }
  toggleVisible = () => {
    this.setState(prevState => ({
      visibility: !prevState.visibility
    }));
  };
  render() {
    return (
      <div>
        <h1 onClick={this.toggleVisible}>{this.props.name}</h1>
        {this.state.visibility && (
          <div>
            <p>{this.props.height}</p>
          </div>
        )}
      </div>
    );
  }
}

在此先感谢您的见解或帮助!

3 个答案:

答案 0 :(得分:2)

您可以在父组件中保留一个对象visible,该对象将具有表示人员索引的键和一个值,该值指示该人员是否可见。这样,您可以在此单个对象中切换人的索引,而不必使用有状态的子组件。

示例

class PersonList extends Component {
  constructor(props) {
    super(props);
    this.state = {
      resource: [],
      visible: {}
    };
  }

  // ...

  toggleVisibility = index => {
    this.setState(previousState => {
      const visible = { ...previousState.visibile };
      visible[index] = !visible[index];
      return { visible };
    });
  };

  render() {
    const mappedPeople = this.state.resource.map((person, i) => (
      <Person
        key={i}
        {...person}
        visible={this.state.visible[i]}
        onClick={() => this.toggleVisibility(i)}
      />
    ));
    return <div>{mappedPeople}</div>;
  }
}

const Person = (props) => (
  <div>
    <h1 onClick={props.onClick}>{props.name}</h1>
    {props.visible && (
      <div>
        <p>{props.height}</p>
      </div>
    )}
  </div>
);

答案 1 :(得分:1)

与@Tholle类似的想法,但是方法不同。假设id对象中有一个person,我们将更改visibles的状态并切换id

class PersonList extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      resource: this.props.persons,
      visibles: {},
    }
  }

  toggleVisible = id => this.setState( prevState => ({
    visibles: { ...prevState.visibles, [id]: !prevState.visibles[id] },
  }))

  render() {
    const mappedPeople =
      this.state.resource.map((person, i) =>
        <Person
          key={person.id}
          visibles={this.state.visibles}
          toggleVisible={this.toggleVisible}
          {...person}
        />
      )
      
    return (
      <div>
        {mappedPeople}
      </div>
    )
  }
}

const Person = (props) => {
  const handleVisible = () =>
    props.toggleVisible( props.id );
  
  return (
    <div>
      <h1 onClick={handleVisible}>
        {props.name}</h1>
      {props.visibles[props.id] &&
        <div>

          <p>{props.height}</p>
        </div>
      }
    </div>
  );
}

const persons = [
  { id: 1, name: "foo", height: 10 },
  { id: 2, name: "bar", height: 20 },
  { id: 3, name: "baz", height: 30 },
]

const rootElement = document.getElementById("root");
ReactDOM.render(<PersonList persons={persons} />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

答案 2 :(得分:0)

您可以确保您的“ this.state.resource”数组在每个对象上都具有可见性标志:

this.state.resource = [
   { ..., visibility: true },
   { ..., visibility: false}
   ...
];

通过稍微修改获取内容来完成此操作。

let fetchedData = await API_Call("people");
this.setState({ 
    resource: fetchedData.results.map(p => ({...p, visiblity: true}))
});

将您的Person组件重新合并到PersonList中(就像您尝试做的那样),然后在onclick上执行以下操作:

onClick={() => this.toggleVisible(i)}

更改toggleVisible()函数以执行以下操作。

toggleVisible = (idx) => {
    const personList = this.state.resource;
    personList[idx].visibility = !personList[idx].visibility;
    this.setState({ resource: personList });      
}

现在,当您执行操作时:

this.state.resource.map((person, i) => ...

...您可以访问“ person.visibility”,并且onclick会切换被点击的特定索引。

不过,我认为这直接回答了您的问题...

我将继续将Person分解为自己的组件,这确实是一个好习惯!

除了更好的组织之外,主要原因之一是避免在道具中使用lamda(我实际上在上面做了)。由于您需要为每个索引执行一次onClick,因此您需要使用数据属性,或者实际上对每个人项目使用React.Component。 您可以在这里进行一些研究: https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md

顺便说一句,您仍然可以创建不是这样的“ React.Component”的“组件”:

import React from 'react';

const Person = ({ exProp1, exProp2, exProp3}) => {
    return <div>{exProp1 + exProp2 + exProp3}</div>
}

Person.propTypes = {
    ...
}

export default Person;

如您所见,React.Component没有继承任何东西,因此您将两全其美(创建组件而不创建“ Components”)。我倾向于这种方法,而不是将所有内容都内联。但是,如果您的应用程序不是很大,而您只想完成它,那么采用第一种方法并不是很糟糕。