RN开发搬砖经验之—在React 函数式组件别一把梭useState得考虑下useRef

本文主要是介绍RN开发搬砖经验之—在React 函数式组件别一把梭useState得考虑下useRef,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在这里插入图片描述

最近在fix一些bug中,发现在函数式组件中不区别场景,任何函数式组件中的变量都是使用useState,然后没有考虑到useState是异步更新值的,导致各种离奇的BUG出现!另外看到相关代码中出现大量的setTimeout操作,估计想用它来规避useState是异步更新值的行为,这种情况下代码就更容易出bug,也很难维护了!

当使用 useState 时,我们如果不正确地处理异步操作,可能会导致意料之外的行为。useState 的调用操作是同步的,其它是异步更新的,如果你在事件处理函数或其他异步回调中直接更新状态,并期望状态立即改变,这可能会导致问题。下面是一个这样的示例代码:

import React, { useState } from 'react';  function BuggyComponent() {  const [count, setCount] = useState(0);  const handleClick = async () => {  // 假设这是一个异步操作,比如网络请求  const response = await fetchSomeData();  // 基于异步操作的结果来更新状态  setCount(prevCount => prevCount + 1);  // 假设我们立即需要使用更新后的状态  console.log(count); // 这里可能会输出旧的值,而不是更新后的值  };  return (  <div>  <p>Count: {count}</p>  <button onClick={handleClick}>Increment Count</button>  </div>  );  
}  // 假设的异步函数,用于模拟网络请求  
async function fetchSomeData() {  return new Promise(resolve => {  setTimeout(() => {  resolve('Data fetched!');  }, 1000);  });  
}  export default BuggyComponent;

useState的特性

useState是React函数组件中用于管理状态(state)的Hook。它接受一个初始状态,并返回一个数组,其中包含当前状态和一个函数,用于更新当前状态。以下是useState的主要特性和注意点:

特性:

响应式:useState是响应式的,当状态改变时,它会触发组件的重新渲染。
接受任意JavaScript值:useState可以接受任何JavaScript值作为初始状态。
返回数组:useState返回一个数组,其中第一个元素是当前的状态值,第二个元素是一个函数,用于更新该状态值。
使用注意事项:

useState的位置:useState应该被放在函数组件的顶层,即在任何条件语句或循环之前。这是因为useState在每次渲染时都会执行,如果在条件语句或循环中使用useState,可能会导致不可预知的结果。
状态的更新:当使用useState返回的更新函数来改变状态时,如果传入的新值与旧值相同(使用Object.is进行浅比较),那么不会触发组件的重新渲染。此外,useState的更新函数不会与之前的状态进行合并,而是直接替换掉之前的状态。因此,在更新对象或数组时,需要注意保存之前的状态。
避免直接修改状态:不应该直接修改useState返回的状态值,而应该使用更新函数来更新状态。这是因为直接修改状态值不会触发组件的重新渲染,这可能会导致视图与状态不一致。
初始状态的设置:useState的初始状态只在组件的第一次渲染时设置。如果初始状态依赖于组件的props,那么应该使用useEffect来更新状态。

useRef的特性

useRef 是 React 的一个 Hook,它返回一个可变的 ref 对象,其 .current 属性可以被设置为一个 DOM 元素或者任何你想要保持引用的值。useRef 有一些独特的特性和使用注意事项:

特性:
稳定性:useRef 创建的 ref 对象在组件的整个生命周期内保持不变。
不触发重新渲染:修改 ref.current 的值不会引发组件的重新渲染。
通用性:ref.current 可以保存任何类型的值,不仅仅是 DOM 元素。
访问性:即使在函数组件的每次渲染中,你都可以通过 ref.current 访问到最新的值。
使用注意事项:
1、不作为依赖项:ref.current 不应作为 useEffect、useMemo 或 useCallback 等其他 Hooks 的依赖项,因为 React 不会跟踪 ref.current 的变化来触发重新渲染。
2、不用于状态管理:由于修改 ref.current 不会触发重新渲染,因此不应使用 useRef 来管理需要在状态变化时更新视图的状态。这是 useState 的主要用途。
3、初始值设置:你可以在创建 useRef 时为其提供一个初始值,但这个初始值只在第一次渲染时设置。之后,你可以通过直接赋值来更改 ref.current 的值。
4、访问 DOM 元素:当 ref 被绑定到一个 DOM 元素时(如

),你可以通过 myRef.current 访问到这个 DOM 元素。这常用于管理焦点、文本选择或媒体播放等。
5、保存可变对象:由于 useRef 创建的 ref 对象在组件生命周期内保持不变,并且修改其 .current 属性不会触发重新渲染,因此它非常适合用于保存可变对象,如定时器 ID、订阅 ID 或可变的数据结构

两者的使用场景

useState和useRef都是React的Hooks,但它们有不同的使用场景和目的。

1、useState主要用于在函数组件中管理状态。当状态发生变化时,组件会重新渲染。它是异步的,同一个函数内设置的,不能实时获取到最新的值。useState的使用场景通常包括需要在状态改变时重新渲染视图的场景。例如,你可以使用useState来创建一个计数器,当计数器的值变化时,整个组件会重新渲染,显示新的计数器值。

2、useRef则主要用于访问DOM元素或者保存对可变对象的引用,这种引用不会触发组件重新渲染。useRef返回的可变对象在组件的整个生命周期内保持不变,且设置的值是同步的,同一个函数内设置的,能实时获取到最新的值。useRef的一个常见用例是将ref对象绑定到DOM元素上,以便在必要时访问DOM元素的属性和方法。此外,useRef也可以用于保存可变对象的引用,而不影响视图的更新。

总的来说,useState主要用于数据的变化和视图的更新,而useRef则主要用于访问和交互不会触发渲染的对象。

需要注意的是,ref.current不可以作为其他hooks(如useMemo, useCallback, useEffect)的依赖项,因为ref.current的值发生变更并不会造成re-render,Reactjs并不会跟踪ref.current的变化。同时,变量(组件内)在每次组件重新渲染的时候都会被重新进行赋值为初始值,所以如果你想要保留之前操作的状态的话就不要使用变量。

比如加列表分页加载时分页变量,其实是可以用useRef来替换useState的,特别是当你的分页变量在更改时,就需要访问(同步获取更新后的值)且分页变量不希望触发组件重新渲染(多数情况下分页变量是不需要触发重新渲染的)这种场景时,就特别需要useRef了。
使用useState的示例如下:

import React, { useState, useEffect } from 'react';  
import { FlatList, View, Text, ActivityIndicator } from 'react-native';  const PageSize = 10; // 每页的项目数量  const MyFlatList = () => {  const [loading, setLoading] = useState(false);  const [dataList, setDataList] = useState([]);  const [hasMore, setHasMore] = useState(true);  const [page, setPage] = useState(1);  // 加载更多数据的函数  const loadMoreData = async () => {  if (loading || !hasMore) return;  setLoading(true);  // 模拟网络请求获取下一页数据  const newData = await fetchData(page);  // 更新数据列表和页面  setDataList(prevData => [...prevData, ...newData]);  setPage(prevPage => prevPage + 1);  // 根据返回的数据判断是否还有更多页面  setHasMore(newData.length === PageSize);  setLoading(false);  };  // 模拟从服务器获取数据的函数  const fetchData = async (page) => {  // 假设这是从服务器获取的数据  // 在实际应用中,你会发送一个网络请求到服务器,并处理响应  const start = (page - 1) * PageSize + 1;  const end = start + PageSize;  return Array.from({ length: PageSize }, (_, i) => `Item ${start + i}`);  };  return (  <FlatList  data={dataList}  keyExtractor={item => item.toString()}  renderItem={({ item }) => <Text>{item}</Text>}  onEndReached={loadMoreData}  onEndReachedThreshold={0.5}  ListFooterComponent={  loading ? (  <View style={{ paddingVertical: 20 }}>  <ActivityIndicator size="large" />  </View>  ) : null  }  />  );  
};  export default MyFlatList;

改成分页变量用useRef的示例如下:

import React, { useState, useEffect, useRef } from 'react';  
import { FlatList, View, Text, ActivityIndicator } from 'react-native';  const PageSize = 10; // 每页的项目数量  const MyFlatList = () => {  const [loading, setLoading] = useState(false);  const [dataList, setDataList] = useState([]);  const [hasMore, setHasMore] = useState(true);  const pageRef = useRef(1); // 使用 ref 来存储当前的页码  // 加载更多数据的函数  const loadMoreData = async () => {  if (loading || !hasMore) return;  setLoading(true);  // 模拟网络请求获取下一页数据  const currentPage = pageRef.current;  const newData = await fetchData(currentPage);  // 更新数据列表和页码  setDataList(prevData => [...prevData, ...newData]);  pageRef.current = currentPage + 1; // 更新 ref 中的页码  // 根据返回的数据判断是否还有更多页面  setHasMore(newData.length === PageSize);  setLoading(false);  };  // 模拟从服务器获取数据的函数  const fetchData = async (page) => {  // 假设这是从服务器获取的数据  // 在实际应用中,你会发送一个网络请求到服务器,并处理响应  const start = (page - 1) * PageSize + 1;  const end = start + PageSize;  return Array.from({ length: PageSize }, (_, i) => `Item ${start + i}`);  };  return (  <FlatList  data={dataList}  keyExtractor={item => item.toString()}  renderItem={({ item }) => <Text>{item}</Text>}  onEndReached={loadMoreData}  onEndReachedThreshold={0.5}  ListFooterComponent={  loading ? (  <View style={{ paddingVertical: 20 }}>  <ActivityIndicator size="large" />  </View>  ) : null  }  />  );  
};  export default MyFlatList;

使用useRef

类组件

代码示例如下:

import React from 'react';  class MyComponent extends React.Component {  constructor(props) {  super(props);  // 初始化实例属性来模拟 ref  this.myRef = React.createRef();  }  componentDidMount() {  // 类似于 useEffect,在组件挂载后访问 DOM 元素  const node = this.myRef.current;  if (node) {  console.log(node); // 输出 DOM 元素  }  }  render() {  return (  <div ref={this.myRef}>  Hello, World!  </div>  );  }  
}  export default MyComponent;

函数式组件

代码示例如下:

import React, { useRef, useEffect } from 'react';  function MyFunctionalComponent() {  // 创建一个 ref 来保存对 DOM 元素的引用  const myRef = useRef(null);  // 使用 useEffect 在组件挂载后和卸载前执行操作  useEffect(() => {  // 组件挂载后  const node = myRef.current;  if (node) {  console.log(node); // 输出 DOM 元素  // 可以执行其他操作,比如设置焦点、监听事件等  }  // 返回一个清理函数,在组件卸载前执行  return () => {  // 组件卸载前,可以执行清理操作,比如移除事件监听器  };  }, []); // 注意依赖项数组为空,确保只在挂载和卸载时运行  return (  <div ref={myRef}>  Click me  </div>  );  
}  export default MyFunctionalComponent;

这篇关于RN开发搬砖经验之—在React 函数式组件别一把梭useState得考虑下useRef的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/778825

相关文章

Vue3 的 shallowRef 和 shallowReactive:优化性能

大家对 Vue3 的 ref 和 reactive 都很熟悉,那么对 shallowRef 和 shallowReactive 是否了解呢? 在编程和数据结构中,“shallow”(浅层)通常指对数据结构的最外层进行操作,而不递归地处理其内部或嵌套的数据。这种处理方式关注的是数据结构的第一层属性或元素,而忽略更深层次的嵌套内容。 1. 浅层与深层的对比 1.1 浅层(Shallow) 定义

JS常用组件收集

收集了一些平时遇到的前端比较优秀的组件,方便以后开发的时候查找!!! 函数工具: Lodash 页面固定: stickUp、jQuery.Pin 轮播: unslider、swiper 开关: switch 复选框: icheck 气泡: grumble 隐藏元素: Headroom

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

【 html+css 绚丽Loading 】000046 三才归元阵

前言:哈喽,大家好,今天给大家分享html+css 绚丽Loading!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏+关注哦 💕 目录 📚一、效果📚二、信息💡1.简介:💡2.外观描述:💡3.使用方式:💡4.战斗方式:💡5.提升:💡6.传说: 📚三、源代码,上代码,可以直接复制使用🎥效果🗂️目录✍️

Hadoop企业开发案例调优场景

需求 (1)需求:从1G数据中,统计每个单词出现次数。服务器3台,每台配置4G内存,4核CPU,4线程。 (2)需求分析: 1G / 128m = 8个MapTask;1个ReduceTask;1个mrAppMaster 平均每个节点运行10个 / 3台 ≈ 3个任务(4    3    3) HDFS参数调优 (1)修改:hadoop-env.sh export HDFS_NAMENOD

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

hdu1171(母函数或多重背包)

题意:把物品分成两份,使得价值最接近 可以用背包,或者是母函数来解,母函数(1 + x^v+x^2v+.....+x^num*v)(1 + x^v+x^2v+.....+x^num*v)(1 + x^v+x^2v+.....+x^num*v) 其中指数为价值,每一项的数目为(该物品数+1)个 代码如下: #include<iostream>#include<algorithm>

嵌入式QT开发:构建高效智能的嵌入式系统

摘要: 本文深入探讨了嵌入式 QT 相关的各个方面。从 QT 框架的基础架构和核心概念出发,详细阐述了其在嵌入式环境中的优势与特点。文中分析了嵌入式 QT 的开发环境搭建过程,包括交叉编译工具链的配置等关键步骤。进一步探讨了嵌入式 QT 的界面设计与开发,涵盖了从基本控件的使用到复杂界面布局的构建。同时也深入研究了信号与槽机制在嵌入式系统中的应用,以及嵌入式 QT 与硬件设备的交互,包括输入输出设

OpenHarmony鸿蒙开发( Beta5.0)无感配网详解

1、简介 无感配网是指在设备联网过程中无需输入热点相关账号信息,即可快速实现设备配网,是一种兼顾高效性、可靠性和安全性的配网方式。 2、配网原理 2.1 通信原理 手机和智能设备之间的信息传递,利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家应用和设备之间的发现。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规Sof

如何在页面调用utility bar并传递参数至lwc组件

1.在app的utility item中添加lwc组件: 2.调用utility bar api的方式有两种: 方法一,通过lwc调用: import {LightningElement,api ,wire } from 'lwc';import { publish, MessageContext } from 'lightning/messageService';import Ca