首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何简化React中Quiz组件的状态更新?

如何简化React中Quiz组件的状态更新?
EN

Stack Overflow用户
提问于 2020-12-02 11:07:41
回答 3查看 88关注 0票数 0

我有两种类型的问题的测验组件(一个正确的答案和问题与免费答案)。我需要向后端发送以下格式的答案:

代码语言:javascript
复制
 [
  {
    questionId: 'test-id',
    answers: ['answerId']// send answer id if this is question with one correct answer
  },
  {
    questionId: 'test-id',
    answers: ['answerId']// send answer id if this is question with one correct answer
  },
  {
    questionId: 'test-id-2',
    freeAnswer: 'some text' // send freeAnswer if it is open ended question
  }
...
]

我创建了两个处理程序:一个用于文本区域,另一个用于单选按钮,

代码语言:javascript
复制
  const handleOptionChange = ( question, answer) => {

      onChangeQuestionAnswer(question, answer, 'oneCorrectAnswer')
  }

  const handleFreeAnswerChange = (value, question) => {
     onChangeQuestionAnswer(question, value, 'freeAnswer')
  }

在父组件中有一个通用的处理程序来处理所有的回答:

代码语言:javascript
复制
const [chosenAnswers, setChosenAnswers] = useState([])



 const handleChangeQuestionAnswer = (
    questionId,
    answerId,
    type
  ) => {
    const foundedId = chosenAnswers.find(item => item.id === questionId)

    if (!foundedId) {
      if (type === 'oneCorrectAnswer') {
        setChosenAnswers([...chosenAnswers, { id: questionId, answers: [answerId] }])
      } else {
        setChosenAnswers([...chosenAnswers, { id: questionId, freeAnswer: answerId }])
      }
    } else {
      const newResultArray = chosenAnswers.map(item => {
        if (item.id !== questionId) {
          return item
        }

        if (type === 'oneCorrectAnswer') {
          return {
            ...item,
            answers: [answerId]
          }
        } else {
          return {
            ...item,
            freeAnswer: answerId
          }
        }
      })

      setChosenAnswers(newResultArray)
    }
  }

然后我就把chosenAnswers发送给API。这种方法很有效,但对我来说它看起来很奇怪,而且开销很大,我可以以某种方式简化这种逻辑吗?

EN

回答 3

Stack Overflow用户

发布于 2020-12-02 11:52:39

数据和处理程序看起来很干净。在父组件中,有一些逻辑重复了四次(返回answerfreeAnswer键以及相关的字符串或数组)。我会把它放到一个变量中:

代码语言:javascript
复制
  const answerForm =
    type === 'oneCorrectAnswer'
      ? { answers: [answerId] }
      : { freeAnswer: answerId };

然后在调用setChosenAnswers时传播该变量,或者在chosenAnswers上映射时返回对象。例如。

代码语言:javascript
复制
    [...chosenAnswers, { id: questionId, ...answerForm }]

这也允许您删除两个if/else,因为除了那个重复的逻辑之外,条件是相同的。

您还可以通过两种方式修改foundedId if/else:

对顺序进行

  1. Reverse并删除负条件变量( some).
  2. Change if/else to ternary认为这不是最佳实践-更具确定性,对变量 (answerToSubmit)的side
    1. Reverse结果的空间更小,然后使用该变量

    调用setChosenAnswsers一次而不是两次

代码语言:javascript
复制
  const answerToSubmit = foundedId
    ? chosenAnswers.map((item) => {
        if (item.id !== questionId) return item;

        return {
          ...item,
          ...answerForm,
        };
      })
    : [...chosenAnswers, { id: questionId, ...answerForm }];

完整代码:

代码语言:javascript
复制
const [chosenAnswers, setChosenAnswers] = useState([]);

const handleChangeQuestionAnswer = (questionId, answerId, type) => {
  const foundedId = chosenAnswers.find((item) => item.id === questionId);

  const answerForm =
    type === 'oneCorrectAnswer'
      ? { answers: [answerId] }
      : { freeAnswer: answerId };

  const answerToSubmit = foundedId
    ? chosenAnswers.map((item) => {
        if (item.id !== questionId) return item;

        return {
          ...item,
          ...answerForm,
        };
      })
    : [...chosenAnswers, { id: questionId, ...answerForm }];

  setChosenAnswers(answerToSubmit);
};

如果您有更多的答案类型,我可能会建议使用switch语句,但总的来说,这会减少重复的逻辑,并使代码更简洁。

票数 1
EN

Stack Overflow用户

发布于 2020-12-02 11:58:45

你可以通过使用ES6功能来重构你的代码,让它更容易阅读,也许文本区域和单选按钮的拆分更改答案处理程序会更好?一个函数应该只做一件事,不要使用太多的if/else语句,如果是我,我会这样修改代码:

代码语言:javascript
复制
const handleOptionChange = (question, answer) => {
    onChangeSelectionQuestionAnswer(question, answer)
}

const handleFreeAnswerChange = (value, question) => {
    onChangeFreeTextQuestionAnswer(question, value)
}

const handleChangeFreeTextQuestionAnswer = (questionId, answer) => {
    const targetAnswer = chosenAnswers.find(item => item.id === questionId)

    let newAnswers = [...chosenAnswers]

    if (!targetAnswer) {
        newAnswers.push({ id: questionId, freeAnswer: answer })
    }
    if (targetAnswer) {
      const idx = newAnswers.indexOf(targetAnswer)
      newAnswers[idx].freeAnswer = answer
    }
    
    setChosenAnswers(newAnswers)
}

const handleChangeSelectionQuestionAnswer = (questionId, answerId) => {
    const targetAnswer = chosenAnswers.find(item => item.id === questionId)

    let newAnswers = [...chosenAnswers]

    if (!targetAnswer) {
        newAnswers.push({ id: questionId, answers: [answerId] })
    }
    if (targetAnswer) {
      const idx = newAnswers.indexOf(targetAnswer)
      newAnswers[idx].answers = [answerId]
    }
    
    setChosenAnswers(newAnswers)
}

你会发现handleChangeFreeTextQuestionAnswerhandleChangeSelectionQuestionAnswer有重复的代码,所以你可以进一步简化

代码语言:javascript
复制
const handleChangeFreeTextQuestionAnswer = (questionId, answer) => {
    handleChangeQuestionAnswer(questionId, answer, 'freeAnswer')
}

const handleChangeSelectionQuestionAnswer = (questionId, answerId) => {
    handleChangeQuestionAnswer(questionId, [answer], 'answers')
}

const handleChangeQuestionAnswer = (questionId, newValue, valueField) => {
    const targetAnswer = chosenAnswers.find(item => item.id === questionId)

    let newAnswers = [...chosenAnswers]

    if (!targetAnswer) {
        newAnswers.push({ id: questionId, [valueField]: newValue })
    }
    if (targetAnswer) {
      const idx = newAnswers.indexOf(targetAnswer)
      newAnswers[idx][valueField]= newValue
    }
    
    setChosenAnswers(newAnswers)
}

如果以后要添加新的题型,只需要添加一个新的handleChangeXXXQuestionAnswer函数,然后调整答案格式和更新字段,调用handleChangeQuestionAnswer,就不需要再添加更多的if/else或者switch语句了。

票数 1
EN

Stack Overflow用户

发布于 2020-12-02 12:05:59

因为您已经有了单独的处理程序,所以我建议只将格式化的answer对象传递给handleChangeQuestionAnswer。例如,您可以将专用问题类型处理程序更改为以下内容

代码语言:javascript
复制
const handleOptionChange = ( id, answer) => {
  onChangeQuestionAnswer({id, answers: [answer]})
}

const handleFreeAnswerChange = (freeAnswer, id) => {
  onChangeQuestionAnswer({id, freeAnswer})
}

至于通用处理程序,您可以使用对象而不是数组来跟踪答案。对于对象,您可以使用与数组相同的扩展语法。多亏了上面的其他更新,你可以真正地将你的通用处理程序简化到一行。请在下面更新函数

代码语言:javascript
复制
const [chosenAnswers, setChosenAnswers] = useState({})

const handleChangeQuestionAnswer = (question) => {  
  setChosenAnswers(answers => {...answers, ...{[question.id]: question}})  
}

注意:我使用一个处理程序来更新状态,以避免任何争用条件。AFAIK这始终是使用钩子设置器函数更新状态的首选方法。

在向服务器提交答案时,使用Object.values()获取数组形式的值。例如:

代码语言:javascript
复制
Object.values(chosenAnswers)
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/65101641

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档