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

kcugc4gi  于 2023-01-08  发布在  React
关注(0)|答案(2)|浏览(186)

下面是我的代码。我如何让我的药水计数真实的更新,同时避免“比以前渲染更多的钩子”错误。
我知道有一种方法可以做到这一点,但我很难理解它是如何工作的。如果有人能很好地解释它,那将是伟大的,因为我将需要这种情况发生在我的建设很多。

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;
blmhpbnm

blmhpbnm1#

我认为您看到的错误是由于在您的JSX中调用usePotion钩子造成的。钩子只能在其他钩子的顶层或其他组件的顶层调用。我不确定usePotionsetPotionMessage的关系。但是它需要在组件的顶层调用,如果你想从usePotion触发某个事件,你需要从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}
}

现在你有了一个钩子,它返回一些关于药剂的状态,并允许你触发其他状态更新:

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

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

最后你需要让player进入react生命周期,你可以把Player.js转换成钩子,或者你可以把player放入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}
  })
}
qjp7pelc

qjp7pelc2#

为了解决药剂计数不更新的问题,简短的答案是你必须使用一个useState钩子,简短的理由是因为react只会在状态改变时重新渲染,并且它足够聪明,只更新那些受状态改变影响的组件。
代码中的一个示例是,当调用setPotionMessage方法并更新potionMessage值时,react所做的唯一更新是针对JSX {potionMessage && <Text style={styles.text}>{potionMessage}</Text>}
此数据需要存储在状态中的长期原因如下:当react最初呈现时,它会编译一个包含所有返回组件的DOM树。这个DOM树看起来与您编写的JSX非常相似,只是增加了一些父标记和其他东西。当且 * 仅当 * 状态发生变化时,React会编译一个新的DOM树。调用Virtual DOM,然后将现有的DOM树与新的虚拟DOM树进行比较,找出发生变化的组件,最后react将更新原始的DOM树,但是它 * 只 * 更新那些已经改变的组件。
继续我上面使用的potionMessage示例,当BattleScreen最初呈现时,Virtual DOM将如下呈现JSX(从return语句中提取):

<View>
      <Text>3</Text>
      <View></View>
      ...
    </View>

但是,一旦调用了setPotionMessage,并且potionMessage的值从''变为一条消息,react就会重新编译Virtual DOM,以确定发生了什么变化。

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

然后,它将原始DOM树与虚拟DOM树进行比较,找出不同之处,当发现不同之处时,它只重新呈现实际DOM树中发生变化的部分。

<View>
      <Text>3</Text>
      <View>
      // ONLY this block is rerendered on the DOM
        <Text>You really need a potion!</Text>
     // ---
      </View>
      ...
    </View>

在代码修改方面,PhantomSpooks列出了需要的修改。正如他们提到的,让玩家进入React生命周期将是关键。

相关问题