【问题标题】:How to simplify updating state of Quiz component in React?如何简化 React 中测验组件的更新状态?
【发布时间】:2020-12-02 03:07:41
【问题描述】:

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

 [
  {
    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
  }
...
]

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

  const handleOptionChange = ( question, answer) => {

      onChangeQuestionAnswer(question, answer, 'oneCorrectAnswer')
  }

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

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

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。这种方法有效,但对我来说它看起来很奇怪而且开销很大,我可以以某种方式简化这个逻辑吗?

【问题讨论】:

  • 嘿,我提交了一个重构版本的代码,该版本删除了一些重复的逻辑并遵循最佳实践(在思考过程中有一些 cmets)。希望对您有所帮助。
  • switch/case和if/else一样,并不能解决问题。我建议你阅读这两本书:重构和清洁代码
  • @chonnychu 我会做的,谢谢

标签: javascript reactjs


【解决方案1】:

您可以使用 ES6 功能重构代码以使其易于阅读,也许 textarea 和 radiobutton 的拆分更改答案处理程序更好? 一个函数应该只做一件事,不要使用太多的if/else语句,如果是我,我会这样改代码:


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有重复的代码,所以你可以进一步简化

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 语句。

【讨论】:

    【解决方案2】:

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

    const handleOptionChange = ( id, answer) => {
      onChangeQuestionAnswer({id, answers: [answer]})
    }
    
    const handleFreeAnswerChange = (freeAnswer, id) => {
      onChangeQuestionAnswer({id, freeAnswer})
    }
    

    对于通用处理程序,您可以使用对象而不是数组来跟踪答案。对于对象,您可以使用与数组相同的扩展语法。并且由于上面的另一个更新,您可以真正将您的一般处理程序简化为一行。请在下方更新功能

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

    注意:我使用处理程序来更新状态以避免任何竞争条件。 AFAIK 这始终是使用 hook setter 函数更新状态的首选方式。

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

    Object.values(chosenAnswers)
    

    【讨论】:

      【解决方案3】:

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

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

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

          [...chosenAnswers, { id: questionId, ...answerForm }]
      

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

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

      1. 颠倒顺序并删除否定条件(某些人认为这不是最佳做法)。
      2. 将 if/else 更改为三元 - 更具确定性,产生副作用的空间更小。
      3. 将结果设置为变量 (answerToSubmit),然后使用该变量调用setChosenAnswsers 一次而不是两次
        const answerToSubmit = foundedId
          ? chosenAnswers.map((item) => {
              if (item.id !== questionId) return item;
      
              return {
                ...item,
                ...answerForm,
              };
            })
          : [...chosenAnswers, { id: questionId, ...answerForm }];
      

      完整代码:

      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 语句,但总体而言,这减少了重复逻辑并使代码更简洁。

      【讨论】:

        猜你喜欢
        • 2014-04-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-10-02
        • 1970-01-01
        • 1970-01-01
        • 2018-11-23
        • 2021-10-07
        相关资源
        最近更新 更多