我在学习如何让我的React Hooks实时更新时遇到了问题。

huangapple go评论77阅读模式
英文:

I'm having trouble learning how to get my React Hooks to update in real time

问题

以下是我的代码。如何使我的药水计数实时更新,同时避免“渲染时钩子数多于上一次渲染”的错误。

我知道有一种方法可以做到这一点,但我很难理解它是如何工作的。如果有人能够很好地解释一下,那将非常好,因为我将需要在我正在构建的项目中经常使用这个功能。

import React, { useState } from 'react';
import { View, Text, Image, StyleSheet } from 'react-native';
import CustomButton from './CustomButton';
import { useFonts } from 'expo-font';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import player from './Player';
import usePotion from './UsePotion';
import { useNavigation } from '@react-navigation/native';

function addPotion() {
  player.potions++;
}

function BattleScreen() {
  const [fontsLoaded, error] = useFonts({
    'Valorax': require('./Fonts/Valorax.otf'),
  });
  const navigation = useNavigation();
  const [potionMessage, setPotionMessage] = useState('');

  if (!fontsLoaded) {
    return <Text>Loading...</Text>;
  }

  return (
    <View style={styles.container}>
      <Text style={styles.text}>{player.potions}</Text>
      {potionMessage && <Text style={styles.text}>{potionMessage}</Text>}
      <View style={styles.topHalf}>
      </View>
      <View style={styles.bottomHalf}>
        <View style={styles.buttonRow}>
          <CustomButton onPress={() => handleAttack()} title='Attack'></CustomButton>
          <CustomButton onPress={() => handleMagic()} title='Magic'></CustomButton>
        </View>
        <View style={styles.buttonRow}>
          <CustomButton onPress={() => usePotion(setPotionMessage)} title='Use Potion'></CustomButton>
          <CustomButton onPress={() => handleRun()} title='Run'></CustomButton>
          <CustomButton onPress={() => navigation.navigate('Home')} title="Home"></CustomButton>
          <CustomButton onPress={() => addPotion()} title="Add Potion"></CustomButton>
        </View>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#000000',
  },
  topHalf: {
    flex: 1,
    color: 'white',
  },
  bottomHalf: {
    flex: 1,
    flexDirection: 'row',
    flexWrap: 'wrap'
  },
  buttonRow: {
    flex: 1,
    flexDirection: 'row',
    justifyContent: 'space-evenly',
    flexWrap: 'wrap'
  },
  text: {
    fontSize: 40,
    color: 'white',
    fontFamily: "Valorax",
  }
});

export default BattleScreen;
英文:

Below is my code. How do I get my potion count to update in real time while also avoiding the "More hooks than in previous render" error.

I know there is a way to do this, but I'm struggling to understand how it works. If someone could explain it well, that would be great because I will need this to happen alot in what I'm building.

import React, { useState } from &#39;react&#39;;
import { View, Text, Image, StyleSheet } from &#39;react-native&#39;;
import CustomButton from &#39;./CustomButton&#39;;
import { useFonts } from &#39;expo-font&#39;;
import { NavigationContainer } from &#39;@react-navigation/native&#39;;
import { createNativeStackNavigator } from &#39;@react-navigation/native-stack&#39;;
import player from &#39;./Player&#39;;
import usePotion from &#39;./UsePotion&#39;;
import { useNavigation } from &#39;@react-navigation/native&#39;;
function addPotion() {
player.potions ++;
}
function BattleScreen() {
const [fontsLoaded, error] = useFonts({
&#39;Valorax&#39;: require(&#39;./Fonts/Valorax.otf&#39;),
});
const navigation = useNavigation()
const [potionMessage, setPotionMessage] = useState(&#39;&#39;);
if (!fontsLoaded) {
return &lt;Text&gt;Loading...&lt;/Text&gt;;
}
return (
&lt;View style={styles.container}&gt;
&lt;Text style={styles.text}&gt;{player.potions}&lt;/Text&gt;
{potionMessage &amp;&amp; &lt;Text style={styles.text}&gt;{potionMessage}&lt;/Text&gt;}
&lt;View style={styles.topHalf}&gt;
&lt;/View&gt;
&lt;View style={styles.bottomHalf}&gt;
&lt;View style={styles.buttonRow}&gt;
&lt;CustomButton onPress={() =&gt; handleAttack()} title=&#39;Attack&#39;&gt;&lt;/CustomButton&gt;
&lt;CustomButton onPress={() =&gt; handleMagic()} title=&#39;Magic&#39;&gt;&lt;/CustomButton&gt;
&lt;/View&gt;
&lt;View style={styles.buttonRow}&gt;
&lt;CustomButton onPress={() =&gt; usePotion(setPotionMessage)} title=&#39;Use Potion&#39;&gt;&lt;/CustomButton&gt;
&lt;CustomButton onPress={() =&gt; handleRun()} title=&#39;Run&#39;&gt;&lt;/CustomButton&gt;
&lt;CustomButton onPress={() =&gt; navigation.navigate(&#39;Home&#39;)} title=&quot;Home&quot;&gt;&lt;/CustomButton&gt;
&lt;CustomButton onPress={() =&gt; addPotion()} title=&quot;Add Potion&quot;&gt;&lt;/CustomButton&gt;
&lt;/View&gt;
&lt;/View&gt;
&lt;/View&gt;
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: &#39;#000000&#39;,
},
topHalf: {
flex: 1,
color: &#39;white&#39;,
},
bottomHalf: {
flex: 1,
flexDirection: &#39;row&#39;,
flexWrap: &#39;wrap&#39;
},
buttonRow: {
flex: 1,
flexDirection: &#39;row&#39;,
justifyContent: &#39;space-evenly&#39;,
flexWrap: &#39;wrap&#39;
},
text: {
fontSize: 40,
color: &#39;white&#39;,
fontFamily: &quot;Valorax&quot;,
}
});
export default BattleScreen;

答案1

得分: 1

I think that the errors you are seeing is caused by calling the usePotion hook in your JSX. Hooks can only be called in the top level of other hooks, or the top level of other components. I'm not sure what usePotion does with setPotionMessage, but it needs to be called at the top level of the component. If there's some event you want to be triggerable from usePotion you need to return it from usePotion:

// usePotion
export default function usePotion(onChange){
  // I assume usePotion would be structured like this
  const [potion,setPotion] = useState({
    hp:20,
    message:''
  })
  // im assuming usePotion have some internal data
  // that you would like to setPotionMessage to 
  const getPotionMessage = ()=>{
    // because you past setPotionMessage I assume that you
    // you want to subscribe to potion message changes?
    onChange?.(potion.message)
    return potion.message
  }
  return { potion,getPotionMessage}
}

Now you have a hook that returns some state about potions, and allows you trigger other state to update:

// top level of component
const {potion,getPotionMessage} = usePotion(setPotionMessage)
.
.
.

<CustomButton onPress={getPotionMessage} title='Use Potion'/>

Finally you need to get player into react lifecycle. You could either convert Player.js into a hook, or you could you could put player into state:

// at the top level of the component
const [playerState,setPlayerState] = useState(player);
//  maybe wrap addPotions in a useCallback?
const addPotion = ()=>{
  setPlayerState(prev=>{
    return {...prev,potions:prev.potions+1}
  })
}
英文:

I think that the errors you are seeing is caused by calling the usePotion hook in your JSX. Hooks can only be called in the top level of other other hooks, or the top level of other components. I'm not sure what usePotion does with setPotionMessage, but it needs to be called at the top level of the component. If there's some event you want to be triggerable from usePotion you need to return it from usePotion:

// usePotion
export default function usePotion(onChange){
  // I assume usePotion would be structured like this
  const [potion,setPotion] = useState({
    hp:20,
    message:&#39;&#39;
  })
  // im assuming usePotion have some internal data
  // that you would like to setPotionMessage to 
  const getPotionMessage = ()=&gt;{
    // because you past setPotionMessage I assume that you
    // you want to subscribe to potion message changes?
    onChange?.(potion.message)
    return potion.message
  }
  return { potion,getPotionMessage}
}

Now you have a hook that returns some state about potions, and allows you trigger other state to update:

// top level of component
const {potion,getPotionMessage} = usePotion(setPotionMessage)
.
.
.

&lt;CustomButton onPress={getPotionMessage} title=&#39;Use Potion&#39;/&gt;

Finally you need to get player into react lifecycle. You could either convert Player.js into a hook, or you could you could put player into state:

// at the top level of the component
const [playerState,setPlayerState] = useState(player);
//  maybe wrap addPotions in a useCallback?
const addPotion = ()=&gt;{
  setPlayerState(prev=&gt;{
    return {...prev,potions:prev.potions+1}
  })
}

</details>



# 答案2
**得分**: 0

为了解决药水计数不更新的问题简单的答案是你需要使用 `useState` 钩子这是因为 React 只会在状态改变时重新渲染而且它足够智能只会更新受到状态变化影响的组件

在你的代码中的一个示例是当调用 `setPotionMessage` 方法并更新 `potionMessage` 值时React 只会更新 JSX 中的 `{potionMessage && <Text style={styles.text}>{potionMessage}</Text>}` 部分

为什么这些数据需要存储在状态中的长理由如下
 React 初始渲染时它会编译所有返回组件的 DOM 这个 DOM 树将与你编写的 JSX 非常相似只是有额外的父级标签和其他内容只有当状态发生变化时React 才会编译一个新的 DOM 称为虚拟 DOM”,其中包含新变化的数据然后它将比较现有的 DOM 树和新的虚拟 DOM 以找出已更改的组件最后React 将更新原始 DOM 但它只会更新那些已更改的组件

继续使用我上面提到的 `potionMessage` 示例 `BattleScreen` 初始渲染时虚拟 DOM 将渲染类似于以下的 JSX `return` 语句中提取):
```jsx
    <View>
      <Text>3</Text>
      <View></View>
      ...
    </View>

然而,一旦调用 setPotionMessage 并且 potionMessage 值从 '' 更改为一条消息,React 会重新编译虚拟 DOM 以确定有哪些变化。它将重新编译为类似于以下内容:

    <View>
      <Text>3</Text>
      <View>
        <Text>You really need a potion!</Text>
      </View>
      ...
    </View>

然后,它将原始 DOM 树与虚拟 DOM 树进行比较,以找出不同之处。当找到不同之处时,它只会重新渲染实际 DOM 树中已更改的部分。

    <View>
      <Text>3</Text>
      <View>
      // 仅重新渲染 DOM 上的这个块
        <Text>You really need a potion!</Text>
     // ---
      </View>
      ...
    </View>

关于所需的代码修改,PhantomSpooks已经概述了所需的更改。正如他们提到的,将玩家引入 React 生命周期将是关键。

英文:

In order solve the potion count not updating, the short answer is that you will have to utilize a useState hook. The short reasoning for this is because react will only rerender when state is changed and it is smart enough to only update the components that have been impacted by the state change.

An example of this in your code is when the setPotionMessage method is called and the potionMessage value is updated, the only update react makes is to the JSX {potionMessage &amp;&amp; &lt;Text style={styles.text}&gt;{potionMessage}&lt;/Text&gt;}.

The long reason for why this data needs to be stored in state is the following:
When react initially renders, it compiles a DOM tree of all of your returned components. This DOM tree will look pretty similar to the JSX you have written, with additional parent tags and other stuff. When and only when there has been a change in state, React will compile a new DOM tree, called the Virtual DOM with the newly changed data. Then it will compare the existing DOM tree to the new Virtual DOM tree to find the components that have changed. Finally, react will update the original DOM tree but it will only update those components that have changed.

Continuing with the potionMessage example I used above. When BattleScreen initially renders, the Virtual DOM will render the JSX like this (pulled from the return statement):

    &lt;View&gt;
&lt;Text&gt;3&lt;/Text&gt;
&lt;View&gt;&lt;/View&gt;
...
&lt;/View&gt;

However, once the setPotionMessage is called and the potionMessage value changes from &#39;&#39; to a message, react recompiles the Virtual DOM to determine what has changed. It will recompile to something like this:

    &lt;View&gt;
&lt;Text&gt;3&lt;/Text&gt;
&lt;View&gt;
&lt;Text&gt;You really need a potion!&lt;/Text&gt;
&lt;/View&gt;
...
&lt;/View&gt;

Then it compares the original DOM tree with the Virtual DOM tree to figure out what is different. When it finds what is different, it rerenders only the part of the tree that has changed on the actual DOM tree.

    &lt;View&gt;
&lt;Text&gt;3&lt;/Text&gt;
&lt;View&gt;
// ONLY this block is rerendered on the DOM
&lt;Text&gt;You really need a potion!&lt;/Text&gt;
// ---
&lt;/View&gt;
...
&lt;/View&gt;

In terms of the code modifications needed, PhantomSpooks outlined the required changes. As they mentioned, getting the player into the react lifecycle will be key.

huangapple
  • 本文由 发表于 2023年1月6日 12:57:32
  • 转载请务必保留本文链接:https://go.coder-hub.com/75027078.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定