React Hooks 实现计数器

Posted

技术标签:

【中文标题】React Hooks 实现计数器【英文标题】:React Hooks to Implement Counter 【发布时间】:2020-04-11 15:59:08 【问题描述】:

我正在尝试在组件中使用 React 挂钩(useState 和 useEffect)来显示投票计数。用户单击上/下后,“投票”状态应该增加或减少,然后应用程序调用 API 来修补数据库中的“投票”计数。原始投票计数来自父组件传递的道具。但由于某种原因,投票的初始状态始终为 0——即使当我 console.log 显示道具时它显示正确的票数?

PATCH 请求似乎工作正常,但我认为带有钩子的东西是错误的。在此先感谢您提供的任何帮助!

export default function Votes(props) 
    const  item, voteCount, itemType  = props
    const [votes, setVotes] = useState(voteCount || 0)

    useEffect(() => 
        if (itemType == 'question') 
                questionApiService.updateQuestionFields(
                    questionId: item.id, 
                    questionFields :  votes: `$votes` 
                )
             else if (itemType === 'answer') 
                console.log('answer')
                // questionApiService.updateAnswerFields(
                //     answerId: item.id,
                //     answerFields:  votes: votes 
                //)
            
        , [votes])

    const handleClick = e => 
        e.currentTarget.id === "increment"
            ? setVotes(prevCount => prevCount + 1)
            : setVotes(prevCount => prevCount - 1)
    

    return (
        <div className="QuestionPage__votes-count">
            <button id="increment" onClick=handleClick>
                <FontAwesomeIcon icon=faCaretUp size="2x" />
            </button>
            votes
            <button id="decrement" onClick=handleClick>
                <FontAwesomeIcon icon=faCaretDown size="2x" />
            </button>
        </div>
    )

【问题讨论】:

将雕像的初始值分配给道具的初始值被认为是反模式。 如果 questionApiService 正在调用远程服务或 API 端点,它可能需要是带有 awaitasync 或使用承诺模式。 所以问题只与组件的初始状态有关? 问题似乎只与初始状态有关(PATCH 有效,单击向上/向下按预期重新呈现屏幕上的计数)。问题是当我刷新页面时,屏幕上显示的投票计数会变回零,即使这不是从道具传递的 voteCount。 我是 React 新手,所以不知道将 props 作为初始状态传递是不好的。我选择这样做是因为这个“投票”组件在多个地方被重用。父组件总是获取一些数据(其中一部分是当前投票计数,这是我想要的初始状态)。我不确定是否有更好的方法来做到这一点...除了将“投票”功能作为自己的组件之外,我可以将其写入每个父级,以便它可以访问那里的数据? 【参考方案1】:

您需要将itemType 添加到useEffect 的依赖项中,因为您不能期望该道具在第一次渲染时可用,或者在整个组件生命周期中保持静态。在您当前的示例中,引用的 itemType 将始终在此函数第一次运行时引用其值。

正如其他人所提到的,从 props 设置状态初始值是禁止的。一旦你的组件收到它的道具,你可以通过使用单独的效果来设置状态来解决这个问题:

...
const [votes, setVotes] = useState(0);
...

useEffect(() => 
  setVotes(voteCount);
, [voteCount]);

【讨论】:

这正是我需要做的!谢谢!一旦我有足够的声望投票,我会回来投票这个答案!【参考方案2】:

您可以像@tobiasfreid 所说的那样使用useState() 来更改计数器状态,或者如果您想创建一个自定义挂钩来实现相同的功能。

function ChangeCounter() 
  const [counter,onChangeValue] = useCounter(0);
  return (
    <div className="Form">
      <button type="button" onClick=onChangeValue(counter + 1)> Increase Counter </button>
    </div>
  );

function useCounter(initialValue)
  const [value, setValue] = useState(initialValue || 0);

  const onChange= (data) => 
    setValue(data)
  ;

  return [value, onChange]; 

通过这种方式,您可以创建自定义挂钩。

【讨论】:

【参考方案3】:

import React from 'react';
import ReactDOM from 'react-dom';

function useCounter(initialCount = 0) 
  const [count, setCount] = React.useState(initialCount);
  const increment = React.useCallback(() => setCount(c => c + 1), []);
  const decrement = React.useCallback(() => setCount(c => c - 1), []);
  return  count, increment, decrement ;


const delay = duration => new Promise(resolve => setTimeout(resolve, duration));

function App() 
  const  count: votes, increment, decrement  = useCounter(0);
  React.useEffect(() => 
    (async () => 
      console.log('Call your API');
      await delay(2000);
      console.log('API Called with votes :', votes);
    )();
  , [votes]);

  return (
    <div>
      <h1>Votes votes</h1>
      <button onClick=decrement>Decrement</button>
      <button onClick=increment>Increment</button>
    </div>
  );


const rootElement = document.getElementById('root');
ReactDOM.render(<App />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

【讨论】:

【参考方案4】:

tobiasfried 下面的回答真的帮助了我。我在这里发布完整的解决方案代码,以防将来可以帮助其他人。当然,如果有人有改进此代码的建议,我很乐意听到。

export default function Votes(props) 
    const  item, itemType  = props
    const [votes, setVotes] = useState()

    useEffect(() => 
        setVotes(item.votes);
    , [item.votes])

    useEffect(() => 
        if (itemType == 'question') 
                questionApiService.updateQuestionFields(
                    questionId: item.id, 
                    questionFields :  votes: votes 
                )
             else if (itemType === 'answer') 
                // questionApiService.updateAnswerFields(
                //     answerId: item.id,
                //     answerFields:  votes: votes 
                //)
            
        , [votes, itemType])

    return (
        <div className="QuestionPage__votes-count">
            <button 
                onClick=() => setVotes(prevCount => prevCount + 1)>
                <FontAwesomeIcon icon=faCaretUp size="2x" />
            </button>
            votes
            <button 
                onClick=() => setVotes(prevCount => prevCount - 1)>
                <FontAwesomeIcon icon=faCaretDown size="2x" />
            </button>
        </div>
    )

【讨论】:

以上是关于React Hooks 实现计数器的主要内容,如果未能解决你的问题,请参考以下文章

React Hooks + Firebase Firestore onSnapshot - 正确使用带有反应钩子的 Firestore 监听器

react框架实现点击事件计数小案例

React Hook 实现导致并排计数变慢

在对计数器函数的React中使用映射函数

Redux及React-Redux概述

react-redux 中的奇怪按钮行为