当前位置: 首页 > news >正文

react 常用hooks封装--useReactive

概述

一种具备响应式的useState

我们知道用useState可以定义变量格式为:

const [count, setCount] = useState(0)

通过 setCount 来进行设置,count 来获取,使用这种方式才能够渲染视图

来看看正常的操作,像这样 let count = 0; count = 7;此时count的值就是7,也就是说数据是响应式的。

那么我们可不可以将 useState 也写成响应式的呢?我们可以自由设置count的值,并且可以随时获取到count的最新值,而不是通过setCount来设置

那么,我们来想想怎么去实现一个具备响应式特点的useState也就是useRactive

提出以下疑问:

  • 这个钩子的出入参如何设定
  • 如何将数据制作成响应式--毕竟普通的操作无法刷新视图
  • 如何优化

分析

以上提出的疑问,最关键的就是第二个,我们如何将数据做成响应式的,要想做成响应式,就必须监听到值的变化,再作出更改也就是说,我们对这个数进行操作的时候,要进行相应的拦截,这里就可以用到es6的:Proxy

在这里会用到Proxy和Reflect

Proxy接受的参数是对象,所以第一个问题也就解决了,入参就为对象。那么如何去刷新视图?这里就需要用到另一个自定义的hooks--useUpdate来强制刷新,使数据更改

至于优化这块,这里会用到另一个自定hooks--useCreation,再配合useRef来放置initalState即可

useUpdate

有的时候我们需要组件强制更新,这个时候就可以使用这个钩子:

hooks/useUpdate.js

// 强制更新
import { useCallback, useState } from "react";const useUpdate = () => {const [, setUpdate] = useState({});return useCallback(() => setUpdate({}), []);
}export default useUpdate;

useCreation

这里封装的 useCreation 是 useMemo 或 useRef 的代替品,换言之useCreation这个钩子增强了useMemo 和 useRef ,让这个钩子可以替换这两个钩子。

  • useMemo的值不一定是最新的值,但useCreation可以保证拿到的值一定是最新的值
  • 对于复杂常量的创建,useRef容易出现潜在的的性能隐患,但useCreation可以避免

这里的性能隐患是指:

接下来我们来看看如何封装一个useCreation,首先我们要明白以下三点:

  • 第一点:先确定参数,useCreation 的参数与 useMemo 的一致,第一个参数是函数,第二个参数参数是可变的数组
  • 第二点:我们的值要保存在useRef中,这样可以将值缓存,从而减少无关的刷新
  • 第三点:更新值的判断,怎么通过第二个参数来判断是否更新 useRef 里的值。

明白了一上三点我们就可以自己实现一个 useCreation

hooks/useCreation.js

import { useRef } from 'react';const depsAreSame = (oldDeps, deps) => {if(oldDeps == deps) return true;for(let i = 0; i < oldDeps.length; i++) {// 判断两个值是否是同一个值if(!Object.is(oldDeps[i], deps[i])) return false;}return true;
}const useCreation = (fn, deps) => {const { current } = useRef({deps,obj: undefined,initialized: false,});if(current.initialized === false || !depsAreSame(current.deps, deps)) {current.deps = deps;current.obj = fn();current.initialized = true;}return current.obj;
}export default useCreation;

验证:

useReactive

hooks/useReactive.js

import { useRef } from 'react';
import useUpdate from "./useUpdate.js";
import useCreation from "./useCreation.js";const observer = (initialVal, cb) => {const proxy = new Proxy(initialVal, {get(target, key, receiver) {const res = Reflect.get(target, key, receiver);return typeof res === 'object' ? observer(res, cb) : Reflect.get(target, key);},set(target, key, val) {const ret = Reflect.set(target, key, val);cb();return ret;}})return proxy;
}const useReactive = (initialState) => {const ref = useRef(initialState);const update = useUpdate();const state = useCreation(() => {return observer(ref.current, () => {update()});}, [])return state;
}export default useReactive;

TS版(仅供参考):

使用

demo.jsx

import React, {  } from 'react';
import useReactive from "../../hooks/useReactive.js";export const Home = () => {const state = useReactive({count: 0,name: '小度小度',flag: true,arr: [],bugs: ['小度', 'react', 'hook'],addBug(bug) {this.bugs.push(bug)},get bugsCount() {return this.bugs.length;},});return (<div className="App" style={{padding: 20}}><div style={{fontWeight: 'bold'}}>基本使用</div><div style={{marginTop: 8}}>对数字进行操作:{state.count}</div><div style={{margin: '8px 0', display: 'flex', justifyContent: 'flex-start'}}><button onClick={() => state.count++}>加1</button><button style={{marginLeft: 8}} onClick={() => state.count--}>减1</button><button style={{marginLeft: 8}} onClick={() => state.count = 7}>设置为7</button></div><div style={{marginTop: 8}}>对字符串进行操作:{state.name}</div><div style={{margin: '8px 0', display: 'flex', justifyContent: 'flex-start'}}><button onClick={() => state.name = '小嘟嘟'}>设置为小嘟嘟</button><button style={{marginLeft: 8}} onClick={() => state.name = 'Demesy'}>设置为Domesy</button></div><div style={{marginLeft: 8}}>对布尔值进行操作:{JSON.stringify(state.flag)}</div><div style={{margin: '8px 0', display: 'flex', justifyContent: 'flex-start'}}><button onClick={() => state.flag = !state.flag}>切换状态</button></div><div style={{marginLeft: 8}}>对数组进行操作:{JSON.stringify(state.arr)}</div><div style={{margin: '8px 0', display: 'flex', justifyContent: 'flex-start'}}><button onClick={() => state.arr.push(Math.floor(Math.random() * 100))}>push</button><button style={{marginLeft: 8}} onClick={() => state.arr.pop()}>pop</button><button style={{marginLeft: 8}} onClick={() => state.arr.shift()}>shift</button><button style={{marginLeft: 8}} onClick={() => state.arr.unshift(Math.floor(Math.random() * 100))}>unshift</button><button style={{marginLeft: 8}} onClick={() => state.arr.reverse()}>reverse</button><button style={{marginLeft: 8}} onClick={() => state.arr.sort()}>sort</button></div><div style={{fontWeight: 'bold', marginTop: 8}}>计算属性:</div><div style={{marginTop: 8}}>数量:{state.bugsCount}个</div><div style={{margin: '8px 0'}}><form onSubmit={(e) => {state.bug ? state.addBug(state.bug) : state.addBug('domesy')state.bug = ''e.preventDefault()}}><input type="text" value={state.bug} onChange={(e) => state.bug = e.target.value}/><button type="submit" style={{marginLeft: 8}}>增加</button><button style={{marginLeft: 8}} onClick={() => state.bug.pop()}>删除</button></form></div><ul>{state.bugs.map((bug, index) => {<li key={index}>{bug}</li>})}</ul></div>);
};

相关文章:

  • 【Y004】基于springboot+vue实现的图书管理系统
  • 利用 Page Visibility API 优化网页性能与用户体验
  • babylon.js-1:入门篇
  • 活体检测标签之2.4G有源RFID--SI24R2F+
  • 计算机毕业设计 基于Python的音乐平台的设计与实现 Python+Django+Vue 前后端分离 附源码 讲解 文档
  • SQL学习1
  • 通过fdisk初始化Linux数据盘
  • Xcode16适配
  • 机器学习(1):机器学习的概念
  • android 系统默认apn数据库
  • Vue 3 魔法揭秘:CSS 解析与 scoped 背后的奇幻之旅
  • 长沙某公司.Net高级开发面试题
  • 实战C++手写线程池
  • 【自用软件】IDM下载器 Internet Download Manager v6.42 Build 10
  • 黑马头条day5- 延迟任务精准发布文章
  • Android优雅地处理按钮重复点击
  • React-Native - 收藏集 - 掘金
  • Redis学习笔记 - pipline(流水线、管道)
  • spring-boot List转Page
  • 聊聊springcloud的EurekaClientAutoConfiguration
  • 如何选择开源的机器学习框架?
  • 实现菜单下拉伸展折叠效果demo
  • 小程序开发中的那些坑
  • 一个项目push到多个远程Git仓库
  • 在Unity中实现一个简单的消息管理器
  • AI算硅基生命吗,为什么?
  • Semaphore
  • ‌[AI问答] Auto-sklearn‌ 与 scikit-learn 区别
  • #Linux(帮助手册)
  • #pragma once
  • (1)(1.19) TeraRanger One/EVO测距仪
  • (19)夹钳(用于送货)
  • (2)MFC+openGL单文档框架glFrame
  • (Arcgis)Python编程批量将HDF5文件转换为TIFF格式并应用地理转换和投影信息
  • (windows2012共享文件夹和防火墙设置
  • (八十八)VFL语言初步 - 实现布局
  • (附源码)spring boot车辆管理系统 毕业设计 031034
  • (附源码)springboot优课在线教学系统 毕业设计 081251
  • (附源码)计算机毕业设计SSM疫情下的学生出入管理系统
  • (教学思路 C#之类三)方法参数类型(ref、out、parmas)
  • (蓝桥杯每日一题)平方末尾及补充(常用的字符串函数功能)
  • (免费领源码)Java#ssm#MySQL 创意商城03663-计算机毕业设计项目选题推荐
  • (三)SvelteKit教程:layout 文件
  • (已解决)vscode如何选择python解释器
  • (原創) 如何解决make kernel时『clock skew detected』的warning? (OS) (Linux)
  • ./mysql.server: 没有那个文件或目录_Linux下安装MySQL出现“ls: /var/lib/mysql/*.pid: 没有那个文件或目录”...
  • .bat批处理(九):替换带有等号=的字符串的子串
  • .NET CF命令行调试器MDbg入门(一)
  • .NET Entity FrameWork 总结 ,在项目中用处个人感觉不大。适合初级用用,不涉及到与数据库通信。
  • .net使用excel的cells对象没有value方法——学习.net的Excel工作表问题
  • /etc/fstab和/etc/mtab的区别
  • @DS 多数据源 + @Transactional(rollbackFor = Exception.class) 导致@DS 多数据源没法使用
  • @EnableConfigurationProperties注解使用
  • @FeignClient注解,fallback和fallbackFactory
  • @param注解什么意思_9000字,通俗易懂的讲解下Java注解