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

IT技术 javascript reactjs
2021-05-10 20:40:50

我试图简单地映射从 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个回答

您可以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>
);

与@Tholle 的想法类似,但方法不同。假设对象中有一个idperson我们正在改变visibles状态并切换ids。

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>

您可以确保您的“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={() => 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 分解成它自己的组件,这确实是一个很好的做法!

除了更好的组织之外,主要原因之一是避免props中的 lamdas(我实际上在上面做过)。因为你需要为每个索引做一个 onClick,你要么需要使用 data 属性,要么实际上为每个 person 项目使用 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 继承,所以你得到了两全其美(创建组件而不创建“组件”)。我会倾向于这种方法,而不是将所有内容都内联。但是,如果您的应用程序不是特别大,而您只是想完成它,那么采用第一种方法并不是非常糟糕。