【问题标题】:React Hooks: set component state without re-rendering twiceReact Hooks:设置组件状态而无需重新渲染两次
【发布时间】:2019-11-12 22:17:34
【问题描述】:

所以我有两个组件:一个 Input 组件,它基本上只是一个按钮,将输入值的当前状态设置为活动,然后将值对象发送到其父组件问题:

import React, { useState, useEffect } from 'react';
import './Input.css';

const Input = (props) => {
    // input state:
    const title = props.title;
    const index = props.index;
    const [active, setActive] = useState(false);
    const [inputValue, setInputValue] = useState({index, title, active});

// sets active status based on what status is in Question component
// the logic there would only allow 1 radio input to be active as opposed to checkboxes where we have multiple active
useEffect(() => {
    setActive(props.active);
}, [props.active]);


// stores activity status of single input and re-runs only when 'active' changes (when clicking the button)
useEffect(() => {
    setInputValue({index, title, active});
}, [active]);

// returns updated input value to Question component
useEffect(() => {
    return props.selected(inputValue);
}, [inputValue]);

return (
    <div className='input'>
        <button 
            data-key={title}
            className={props.active ? 'highlight' : ''}
            onClick={() => setActive(active => !active)}
        >
            {title}
        </button>
    </div>
);
}

export default Input;

并且 Question 检查当前问题类型(它从另一个父组件接收)是否是“单选”按钮类型,在这种情况下,您只能有一个选项。所以目前我是这样设置的:

import React, { useState, useEffect } from 'react';
import s from './Question.css';
import Input from './Input/Input';

const Question = (props) => {
    // create intitial state of options
    let initialState = [];
    for (let i=0; i < props.options.length; i++) {
        initialState.push(
            {
                index: i,
                option: props.options[i],
                active: false,
            }
        )
    }

    // question state:
    let questionIndex = props.index;
    let questionActive = props.active;
    let questionTitle = props.question;
    let questionType = props.type;
    let [questionValue, setQuestionValue] = useState(initialState);
    let [isAnswered, setIsAnswered] = useState(false);

    useEffect(() => {
        console.log(questionValue);
    }, [questionValue]);

    // stores currently selected input value for question and handles logic according to type
    const storeInputValue = (inputValue) => {
        let questionInputs = [...questionValue];
        let index = inputValue.index;

        // first set every input value to false when type is radio, with the radio-type you can only choose one option
        if (questionType === 'radio') {
            for (let i=0; i < questionInputs.length; i++) {
                questionInputs[i].active = false;
            }
        }

        questionInputs[index].active = inputValue.active;
        setQuestionValue([...questionInputs]);

        // set state that checks if question has been answered
        questionValue.filter(x => x.active).length > 0 ? setIsAnswered(true) : setIsAnswered(false);
    }

    // creates the correct input type choices for the question
    let inputs = [];
    for (const [index, input] of props.options.entries()) {
        inputs.push(
            <Input
                key={index}
                index={index}
                title={input}
                active={questionValue[index].active}
                selected={storeInputValue}
            />
         );
    }

    // passes current state (selected value) and the index of question to parent (App.js) component
    const saveQuestionValue = (e) => {
        e.preventDefault();
        props.selection(questionValue, questionIndex, questionTitle);
    }

    return (
        <div className={`question ${!questionActive ? 'hide' : ''}`}>
            <h1>{props.question}</h1>
            <div className="inputs">
                {inputs}
            </div>
            <a className={`selectionButton ${isAnswered ? 'highlight' : ''}`} href="" onClick={e => saveQuestionValue(e)}>
                <div>Save and continue -></div>
            </a>
        </div>
    );
}

export default Question;

通过此设置,当我单击输入时,它会将其发送到 Question 组件,并且该组件将 prop.active 返回到 Input,因此它会突出显示输入值。但是当我单击一个新输入时,它会重新渲染两次,因为它会监听输入中的活动状态变化,并将所有输入设置为 false。

我的问题是:如何在此代码中设置逻辑以像无线电输入一样操作,以便它只将当前选定的输入设置为活动,而不是首先将每个输入设置为活动 = false?

【问题讨论】:

    标签: javascript reactjs


    【解决方案1】:

    您不应该在两个不同的组件中复制 state 的值。对于值的状态,应该总是只有一个单一的真实来源。这是 React 最重要的模式之一,甚至在 the official documentation 中也提到过。

    相反,您应该lift shared state up 使其生活在“最近的共同祖先”处。你的&lt;Input&gt; 组件根本不应该有任何内部状态——它们应该是纯函数,除了渲染当前值并提供回调来更新所述值之外什么都不做。但是,他们自己不存储该值 - 它作为道具传递给他们。

    输入有效的所有逻辑以及它具有的值都应该存在于父组件中并从那里向下传递。每当您在子组件中设置状态然后以某种方式将该状态传递回父组件时都是一个警告标志,因为在 React the data should flow down 中。

    【讨论】:

    • 感谢您的解释。我会按照这个逻辑重新构建代码,然后再试一次!
    • 我已经尝试过应用它,它现在可以工作了,非常感谢您的帮助!对于任何在这个问题上苦苦挣扎的人:我在 中所做的是使用当前输入的索引对 执行回调 onClick 函数,在 中我将当前选择的输入设置为 true 以便它突出显示正确的输入。
    • @ÜmitKiliç 很高兴我能提供帮助。请投票并接受我的回答,以表明您的问题已得到解决。 :)
    猜你喜欢
    • 2020-06-26
    • 2021-09-09
    • 1970-01-01
    • 2019-07-28
    • 2019-07-31
    • 2020-07-07
    • 2020-02-05
    • 2020-10-09
    相关资源
    最近更新 更多