更新页面上的列表而不重置 React Native 中的状态

Adr*_*sso 5 javascript react-native firebase-realtime-database react-hooks

我有一个Host.js文件,其中包含一个曲目列表,该列表根据 setTrackListener() 函数的定义进行更新。我的问题是,每次调用该函数时,trackList 实际上并未更新。这是打印输出的示例:

Change detected in setTrackListener
 LOG  Track Name: TEST
 LOG  Current trackList:
 LOG  NewArray: TEST
 LOG  trackList after set:
Run Code Online (Sandbox Code Playgroud)

这是我的代码:

let authToken = "";
let roomID = "";

export default function Host({ navigation }) {
  if (roomID == "") {
    roomID = createRoom();
  }
  const [trackList, setTrackList] = useState([]);

  if (authToken == "") {
    getAuthAccessToken().then((t) => (authToken = t));
  }

  useEffect(() => {
    setTrackListener(roomID, (t) => {
      if (t != null) {
        console.log("Track Name: " + t.name);
        console.log("Current trackList: " + trackList);
        const newArray = [...trackList];
        newArray.push(t.name);
        console.log("NewArray: " + newArray);
        setTrackList(newArray);
        console.log("trackList after set: " + trackList);
      }
    });
 
  }, []);

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Hosting {roomID}</Text>
      <FlatList
        data={trackList}
        renderItem={({ item }) => <Text style={styles.item}>{item}</Text>}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
    alignItems: "center",
    justifyContent: "center",
  },
  title: {
    color: "#590",
    fontSize: 32,
  },
});

Run Code Online (Sandbox Code Playgroud)

setTrackListener是在一个单独的文件中定义的,我在其中监听特定 Google Firebase 数据库的更改。这是该代码:

let setTrackListener = (id, onChange) => {
  let tracksRef = ref(database, `rooms/${id}/tracks`);
  onChildAdded(tracksRef, (snapshot) => {
    const data = snapshot.val();
    console.log("Change detected in setTrackListener");
    onChange(data);
  });
};
Run Code Online (Sandbox Code Playgroud)

如果我使用如下按钮更新 trackList,它会更新:

<Button
  title={"Test"}
  onPress={() => {
  const newArray = [...trackList, "TEST"];
  setTrackList(newArray);
  console.log(trackList);
}}
></Button>
Run Code Online (Sandbox Code Playgroud)

Tys*_*n Z 1

TLDR:将函数setTrackList(newArray);内部转换useEffect

setTrackList((trackList) => [...trackList, t.name]);
Run Code Online (Sandbox Code Playgroud)

我认为你对 setTrackListener 的实现是正确的。

所以我认为主要原因是trackListin 中的变量useEffect被包装在一个过时的闭包中,这是 Javascript 的一个特征,当你定义一个函数,并且它使用该函数外部的变量时,它将缓存该变量的值在定义函数时,并且始终返回相同的值(即本例中为空数组)。

要解决这个问题,您需要将trackList变量放入 的依赖数组中useEffect,或者将回调函数传递给 setState 函数以避免访问trackList内部useEffect

建议:将setTrackList(newArray);useEffect 函数中的转换为

setTrackList((trackList) => [...trackList, t.name]);
Run Code Online (Sandbox Code Playgroud)

并且不要console.log(trackList)在回调函数中使用(你不会看到该值,因为它总是[]),或者将控制台日志放入函数体(相当脏,仅用于调试),或者在 的setTrackList回调中打印该值

setTrackList((trackList) => {
  const newArray = [...trackList, t.name]
  console.log(newArray)
  return newArray
});
Run Code Online (Sandbox Code Playgroud)

如果这不起作用,您在输出中没有看到更新的 trackList 的另一个可能原因是React 不会立即更新状态,该setState函数将触发重新渲染,并且状态会在下一个批次中更新重新渲染。

但在许多情况下,更新会立即生效,但您不能依赖这一点。


PS 需要注意的一点是,卸载组件时useEffect并不会取消订阅onChildAdded,这可能会导致一些性能问题,因此最好重写setTrackListeneruseEffect

setTrackList((trackList) => {
  const newArray = [...trackList, t.name]
  console.log(newArray)
  return newArray
});
Run Code Online (Sandbox Code Playgroud)

参考:

https://dmitripavlutin.com/react-hooks-stale-closures/#3-state-closure-in-useeffect

https://blog.logrocket.com/why-react-doesnt-update-state-immediately/