ReactJS:类型数组的状态在重新渲染期间被分解为元素

时间:2019-08-13 20:48:51

标签: javascript reactjs react-hooks react-component react-state

我要做什么

我有一个包含“配置文件”数组的主要组件。对于每个配置文件,我有两个不同的“条件”组件(一次仅显示一个)。这两个组件中的每一个都有一个按钮,该按钮应该在单击时切换组件。因此,我已将状态提升到主要组件,并使用“ useState”钩子创建了组件状态(该数组是一个数组,其中每个索引是一个字符串,代表针对profiles数组中每个元素显示的子组件),为这些点击创建了两个事件处理函数,并将它们作为渲染道具传递给了它们的子组件。

它们从第一个子组件开始。

问题及其发现方式

按按钮切换到其他组件时,它起作用。当您按下按钮返回时,它崩溃了。说“ TypeError:无法分配给字符串'large'的只读属性'0'”。我在useState初始化之后以及每个函数中的状态更改调用之后放置了一些console.log(state)。发生的事情(只有一个元素的测试列表)是

  1. 组件初始化时,状态显示为['normal'](原始状态,良好)
  2. 单击第一个组件的按钮时,['normal']变为['large'](按预期)
  3. 现在重新渲染组件时,状态变为“大”(不再是数组)
  4. 单击第二个组件的按钮时,应用程序崩溃,因为它不再是数组,因此无法更改数组元素

主要组件

const Peers = props => {
    let dummyPeer = {
        _id: "9asdf98sj3942j4fs9ji",
        user: {
            name: "Test Peer",
            avatar: "//www.gravatar.com/avatar/cd56136f6d9abfdf4a0198dc9ce656c8?s=200&r=pg&d=mm"
        },
        bio: "Biography for Test Peer",
        year: "2022",
        courses: [
            "CISC124",
            "PSYC223",
            "PSYC236",
            "COMM200",
            "CISC251"
        ]
    }

    let profiles = [];
    profiles.push(dummyPeer);

    let initialState = [];
    profiles.forEach(profile => {
        initialState.push("normal");
    });

    let [viewState, setViewState] = useState(initialState);
    console.log(viewState);

    const openLargeView = (id) => {
        let changeIndex = profiles.map(profile => profile._id).indexOf(id);
        setViewState(state => state[changeIndex] = "large");
        console.log(viewState);
    }

    const closeLargeView = (id) => {
        let changeIndex = profiles.map(profile => profile._id).indexOf(id);
        setViewState(state => state[changeIndex] = "normal");
        console.log(viewState);
    }

    return (
        <Fragment>
            {profiles.map((profile, index) => (<Fragment key={profile._id} >
                {viewState[index] === "normal" ? (
                    <Peer openLargeView={openLargeView} profile={profile} />
                ) : (
                    <ViewPeer closeLargeView={closeLargeView} profile={profile} />
                )}
            </Fragment>))}
        </Fragment>
    )
}

子组件1:

const Peer = ({ profile, openLargeView }) => {
    const { _id, user, bio, year, courses } = profile;
    const { avatar } = user;

    return (<Fragment>
        <div className="card-row">
            <div className="profile-header">
                <h1 className="peer-text row-title"> {user.name} </h1>
                <p className="peer-text peer-small"> {year} </p>
                <img className="avatar avatar-peer-small" src={avatar} alt='' />
            </div>
            <button onClick={() => openLargeView(_id)} className="btn-small"> More </button>
        </div>
     </Fragment>)
}

子组件2:

const ViewPeer = ({ profile, closeLargeView }) => {
    const { _id, user, bio, year, courses } = profile;
    const { avatar } = user;

    let courseElements = courses.map((course, index) =>
    <li key={index} className="profile-text"> {course} </li>
    );

    return (
        <Fragment>
            <div className="card-md peer-card">
                <div className="profile-header">
                    <h1 className="peer-text"> {user.name} </h1>

                    <img className="avatar avatar-peer" src={avatar} alt='' />
                </div>

                <div className="profile-info">
                    <h2 className="profile-text"> {bio} </h2>
                    <h2 className="profile-text2"> Year: {year} </h2>
                    <ul className="course-list"> {courseElements} </ul>

                    <div className="profile-button-group">
                        <button onClick={() => closeLargeView(_id)} className="btn-small"> Close </button>
                        <button className="btn-small"> Send Buddy Request </button>
                    </div>
                </div>
            </div>
            </Fragment>
    )
}

预期结果和实际结果

我希望当单击第一个组件的按钮但状态变成字符串数组并导致应用崩溃时,它会返回到原始组件。

1 个答案:

答案 0 :(得分:2)

这里的问题是在viewStateopenLargeView()中更新closeLargeView()的方式。

调用这些函数时,对setViewState的调用会调用状态更改回调,该回调实际上将viewState的类型从数组更改为字符串:

/* 
Summary of problem with following line of code:
1. The statement: state[changeIndex] = "large" returns the string "large"
2. When executed, the statement returns the "large" string from the callback
3. The viewState therefore becomes a string with value "large"
*/
setViewState(state => state[changeIndex] = "large"); 

考虑将这些状态更新修改为以下内容:

setViewState(state => {
    /* 
    1. Shallow clone state into a new array with ... spread
    2. Assign value of "large" to the "changeIndex" in cloned array
    3. Return cloned array as new state for viewState
    */
    const arrayClone = [...state];
    arrayClone[changeIndex] = "large";
    return arrayClone;
});

这可确保通过setViewState()回调传递回您的组件的状态是数组类型,这正是您的组件所期望的。显示需要进行所有更改的更完整的示例是:

const Peers = props => {

    const profiles = [{
        _id: "9asdf98sj3942j4fs9ji",
        user: {
            name: "Test Peer",
            avatar: "//www.gravatar.com/avatar/" + 
                    "cd56136f6d9abfdf4a0198dc9ce656c8?s=200&r=pg&d=mm"
        },
        bio: "Biography for Test Peer",
        year: "2022",
        courses: [
            "CISC124",
            "PSYC223",
            "PSYC236",
            "COMM200",
            "CISC251"
        ]
    }]

    let [viewState, setViewState] = useState(["normal"]);

    const openLargeView = (id) => {
        let changeIndex = profiles.map(profile => profile._id).indexOf(id);
        setViewState(state => {
            const arrayClone = [...state];
            arrayClone[changeIndex] = "normal";
            return arrayClone;
        });
    }

    const closeLargeView = (id) => {
        let changeIndex = profiles.map(profile => profile._id).indexOf(id);
        setViewState(state => {
            const arrayClone = [...state];
            arrayClone[changeIndex] = "large";
            return arrayClone;
        });
    }

    return (
        <Fragment>
            {profiles.map((profile, index) => (<Fragment key={profile._id} >
                {viewState[index] === "normal" ? (
                    <Peer openLargeView={openLargeView} profile={profile} />
                ) : (
                    <ViewPeer closeLargeView={closeLargeView} profile={profile} />
                )}
            </Fragment>))}
        </Fragment>
    )
}

希望有帮助!