普通视图
Nuxt3中PC端与移动端适配的三种方式(含Nuxt官网同款实现方式)
一文讲解时下比较火的Rust语言之-rust开发环境搭建
Next.js 15.4 正式发布:Turbopack 全面稳定,预热 Next.js 16 😍😍😍
聊聊前端请求拦截那些事
DocumentFragment:高性能DOM操作
0基础进大厂,React框架基础篇:创建你的第一个React框架项目——梦开始的地方
从JS到TS:我们放弃了自由,却赢得了整个世界
深入 JavaScript 事件循环:单线程如何掌控异步世界
微前端之子应用的启动与改造
《前端基建实战:高复用框架封装与自动化NPM发布指南》
Fitten Code使用体验
React 表单界的宫斗大戏:受控组件 VS 非受控组件,谁才是正宫娘娘?
🚀 深入Vue3核心:render函数源码解析与实战指南
RTK Query 完全指南:简化数据获取与状态管理
什么是 RTK Query?
RTK Query 是 Redux Toolkit 官方提供的数据获取和缓存解决方案。它专为解决现代应用中的数据获取需求而设计,通过自动化缓存管理、数据获取和状态更新,显著减少数据管理所需的样板代码。
核心优势
- ⚡️ 零配置数据缓存:自动管理缓存生命周期
- 📦 内置加载状态:自动跟踪请求状态(loading/success/error)
- 🔄 自动数据同步:通过标签系统实现自动刷新
- 🔌 高度可扩展:轻松与现有 Redux 逻辑集成
- 🧩 TypeScript 优先:提供完整的类型安全支持
快速入门指南
安装依赖
npm install @reduxjs/toolkit react-redux
基础使用示例
1. 创建 API 服务
// apiSlice.js
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
export const apiSlice = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
tagTypes: ['Post'], // 定义标签类型
endpoints: (builder) => ({
getPosts: builder.query({
query: () => '/posts',
providesTags: ['Post'] // 为此查询提供'Post'标签
}),
addPost: builder.mutation({
query: (newPost) => ({
url: '/posts',
method: 'POST',
body: newPost
}),
invalidatesTags: ['Post'] // 使所有'Post'标签失效
})
})
})
export const { useGetPostsQuery, useAddPostMutation } = apiSlice
2. 配置 Redux Store
// store.js
import { configureStore } from '@reduxjs/toolkit'
import { apiSlice } from './apiSlice'
export const store = configureStore({
reducer: {
[apiSlice.reducerPath]: apiSlice.reducer
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(apiSlice.middleware)
})
3. 在组件中使用
// PostsComponent.jsx
import { useGetPostsQuery, useAddPostMutation } from './apiSlice'
function Posts() {
const { data: posts, isLoading, isError } = useGetPostsQuery()
const [addPost] = useAddPostMutation()
const handleSubmit = async () => {
await addPost({ title: 'New Post', content: 'RTK Query is awesome!' })
}
if (isLoading) return <div>Loading...</div>
if (isError) return <div>Error loading posts</div>
return (
<div>
<button onClick={handleSubmit}>Add Post</button>
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
)
}
与 Axios 集成(自定义 baseQuery)
RTK Query 默认使用 fetchBaseQuery
(基于 Fetch API),但可以轻松集成 Axios:
// axiosBaseQuery.js
import axios from 'axios';
const axiosBaseQuery = ({ baseUrl } = { baseUrl: '' }) =>
async ({ url, method, data, params }) => {
try {
const result = await axios({
url: baseUrl + url,
method,
data,
params
})
return { data: result.data }
} catch (axiosError) {
return {
error: {
status: axiosError.response?.status,
data: axiosError.response?.data || axiosError.message
}
}
}
}
// 在 createApi 中使用
export const apiSlice = createApi({
reducerPath: 'api',
baseQuery: axiosBaseQuery({ baseUrl: '/api' }),
// ...其他配置
})
RTK Query 与 Redux 的关系
RTK Query 是 Redux Toolkit 生态系统的一部分:
- 构建在 Redux 之上:使用 Redux store 存储所有状态
- 无缝集成:与现有 Redux 逻辑并存,共享同一个 store
- 自动生成 reducer 和 action:无需手动编写 reducer 逻辑
- 中间件驱动:使用自定义中间件管理缓存生命周期
graph TD
A[React组件] --> B[使用 useQuery/useMutation]
B --> C[RTK Query API]
C --> D[Redux Store]
D --> E[自动管理状态]
E --> F[缓存/加载/错误状态]
自动化缓存管理:标签系统
RTK Query 的标签系统是其最强大的特性之一,通过 providesTags
和 invalidatesTags
实现自动缓存失效和重新获取。
标签系统工作原理
-
标记数据关系:
endpoints: builder => ({ getPosts: builder.query({ query: () => '/posts', providesTags: ['Post'] // 此查询提供'Post'标签 }), addPost: builder.mutation({ query: (post) => ({ /* ... */ }), invalidatesTags: ['Post'] // 使所有'Post'标签失效 }) })
-
精确缓存控制(使用 ID):
getPost: builder.query({ query: (id) => `/posts/${id}`, providesTags: (result, error, id) => [{ type: 'Post', id }] }), updatePost: builder.mutation({ query: ({ id, ...patch }) => ({ url: `/posts/${id}`, method: 'PATCH', body: patch }), invalidatesTags: (result, error, { id }) => [{ type: 'Post', id }] })
-
组合标签:
getUserPosts: builder.query({ query: (userId) => `/users/${userId}/posts`, providesTags: (result, error, userId) => result ? [ ...result.map(post => ({ type: 'Post', id: post.id })), { type: 'UserPosts', id: userId } ] : ['UserPosts'] })
当执行变更操作(mutation)时,所有关联的标签会自动失效,触发相关查询的重新获取。
对比其他状态管理方案
特性 | RTK Query | Redux + Thunk/Saga | React Query | SWR |
---|---|---|---|---|
学习曲线 | 中等 (需Redux知识) | 高 | 低 | 低 |
缓存管理 | 自动 | 手动 | 自动 | 自动 |
数据获取抽象 | 高级抽象 | 低级实现 | 高级抽象 | 高级抽象 |
与Redux集成 | 原生支持 | 原生支持 | 需要额外集成 | 需要额外集成 |
自动重新获取 | 通过标签系统 | 手动实现 | 通过键/依赖 | 通过键/依赖 |
加载状态管理 | 自动 | 手动 | 自动 | 自动 |
变更操作 | 内置mutation | 手动实现 | 内置mutation | 需要额外实现 |
RTK Query 的独特优势
-
Redux 生态集成:
- 与现有 Redux store 无缝协作
- 可直接访问 Redux DevTools 调试
- 与 Redux Thunk/其他中间件兼容
-
极少的样板代码:
// 传统 Redux 异步 action const fetchPosts = () => (dispatch) => { dispatch(postsLoading()) axios.get('/posts') .then(res => dispatch(postsReceived(res.data))) .catch(err => dispatch(postsFailed(err.message))) } // RTK Query 等效实现 getPosts: builder.query({ query: () => '/posts' })
-
智能缓存策略:
- 相同请求的自动去重
- 组件卸载后保留数据缓存
- 窗口重新聚焦时自动刷新
-
自动生成的 React Hooks:
-
useQuery
:处理数据获取 -
useMutation
:处理数据变更 -
useLazyQuery
:按需触发查询
-
降低上手门槛的技巧
-
利用代码生成:从 OpenAPI/Swagger 规范自动生成 API slice
npx @rtk-query/codegen-openapi openapi-config.json
-
从简单场景开始:
// 最小化配置 export const api = createApi({ baseQuery: fetchBaseQuery({ baseUrl: '/api' }), endpoints: () => ({}) })
-
逐步添加标签:
- 开始时可以不使用标签
- 需要缓存失效时再添加
providesTags/invalidatesTags
-
重用查询参数:
// 在多个组件中使用相同查询 function ComponentA() { const { data } = useGetPostsQuery() // ... } function ComponentB() { // 自动重用缓存数据 const { data } = useGetPostsQuery() // ... }
-
高级场景处理:
// 条件查询 const { data } = useGetPostQuery(id, { skip: !id // 当id不存在时跳过查询 }); // 轮询查询 const { data } = useGetPostsQuery(undefined, { pollingInterval: 5000 // 每5秒刷新一次 });
总结
RTK Query 彻底改变了 Redux 应用中的数据管理方式:
- 🚀 减少约 80% 的数据管理样板代码
- 🔄 通过标签系统实现智能缓存管理
- ⚙️ 无缝集成现有 Redux 架构
- 📡 支持多种通信协议(REST/GraphQL/WebSockets)
- 🧪 提供完整的类型安全(TypeScript)
对于已经在使用 Redux 的项目,RTK Query 提供了最平滑的数据获取升级路径。即使是不熟悉 Redux 的开发者,也能通过其简洁的 API 快速上手高效的数据管理。
开始尝试 RTK Query,体验现代 React 应用数据管理的最佳实践!
官方资源:
针对初学者的JS八种类型实用小技巧总结
一、!!
和 !!!
的深入理解
1. !!
(双重非)操作符
将任意值强制转换为布尔类型,等效于 Boolean()
函数。
转换规则:
-
假值(
null
、undefined
、0
、''
、NaN
、false
)→false
-
其他值 →
true
(包括空数组[]
、空对象{}
、函数等)
典型应用场景:
javascript
// 判断对象是否存在
const user = null;
console.log(!!user); // false
// 简化条件判断
if (!!items.length) { /* 处理非空数组 */ }
// 在Vue项目中判断数据状态
const isLoggedIn = !!user.token;
2. !!!
(三重非)操作符
先通过!!
转换为布尔值,再取反一次,等效于 !Boolean(值)
。
典型应用场景:
javascript
// 简化反向逻辑判断
const isEmpty = !!!value; // 等效于 value === null || value === undefined || value === ''
// 在Vue中处理加载状态
loading.value = !!!data; // 数据存在时隐藏加载状态
二、JavaScript 基础实用技巧
1. 空值合并与默认值处理
javascript
// 传统写法(缺陷:0、''、false 也会被替换)
const name = user.name || '默认名称';
// 推荐写法(仅替换 null/undefined)
const name = user.name ?? '默认名称';
// 对象解构默认值
const { age = 18, address = {} } = user;
2. 可选链操作符(Optional Chaining)
javascript
// 传统写法
const city = user && user.address && user.address.city;
// 简洁写法
const city = user?.address?.city;
// 结合空值合并
const city = user?.address?.city ?? '未知城市';
3. 快速数值转换
javascript
const strNum = '123';
const num = +strNum; // 等效于 Number(strNum)
// 取整技巧
const floatNum = 3.14;
const intNum = ~~floatNum; // 双波浪号取整,等效于 Math.floor(3.14)
4. 数组去重
javascript
const arr = [1, 2, 2, 3, 3, 3];
const uniqueArr = [...new Set(arr)]; // [1, 2, 3]
5. 交换变量值
javascript
let a = 1, b = 2;
// 传统写法
const temp = a;
a = b;
b = temp;
// 简洁写法
[a, b] = [b, a];
三、函数与作用域技巧
1. 函数参数默认值
javascript
// 传统写法
function greet(name) {
name = name || 'Guest';
console.log(`Hello, ${name}`);
}
// 推荐写法
function greet(name = 'Guest') {
console.log(`Hello, ${name}`);
}
2. 箭头函数简化
javascript
// 传统函数
const sum = function(a, b) {
return a + b;
};
// 箭头函数
const sum = (a, b) => a + b;
3. 立即执行函数(IIFE)
javascript
// ES5常用
(function() {
const privateVar = '私有变量';
// 私有作用域
})();
// ES6模块替代方案
{
const privateVar = '私有变量';
// 块级作用域
}
四、对象与数组操作技巧
1. 对象浅拷贝
javascript
const obj = { a: 1, b: 2 };
const clone = { ...obj }; // 展开语法
// 等效于 Object.assign({}, obj)
2. 数组合并
javascript
const arr1 = [1, 2];
const arr2 = [3, 4];
const merged = [...arr1, ...arr2]; // [1, 2, 3, 4]
3. 数组过滤与映射
javascript
const numbers = [1, 2, 3, 4, 5];
// 过滤偶数并翻倍
const result = numbers
.filter(n => n % 2 === 0) // [2, 4]
.map(n => n * 2); // [4, 8]
4. 解构赋值高级用法
javascript
// 对象解构重命名
const { name: userName, age: userAge } = user;
// 数组解构
const [first, second, ...rest] = [1, 2, 3, 4, 5];
五、异步编程技巧
1. 异步函数简化
javascript
// 传统Promise
fetchData()
.then(data => processData(data))
.catch(error => console.error(error));
// 推荐:async/await
async function fetchAndProcess() {
try {
const data = await fetchData();
const result = processData(data);
} catch (error) {
console.error(error);
}
}
2. 并行请求处理
javascript
// 多个API并行请求
async function fetchAll() {
const [user, posts] = await Promise.all([
fetchUser(),
fetchPosts()
]);
return { user, posts };
}
3. 防抖与节流
javascript
// 防抖函数(避免频繁触发)
const debounce = (fn, delay) => {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
};
// 节流函数(限制执行频率)
const throttle = (fn, limit) => {
let inThrottle;
return (...args) => {
if (!inThrottle) {
fn(...args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
};
六、性能优化技巧
1. 延迟加载(懒加载)
javascript
// 按需加载模块
const loadHeavyModule = async () => {
const heavyModule = await import('./heavy-module.js');
heavyModule.init();
};
// 点击按钮时加载
button.addEventListener('click', loadHeavyModule);
2. 循环优化
javascript
// 传统for循环(性能最优)
for (let i = 0, len = arr.length; i < len; i++) {
// ...
}
// 避免使用for...in遍历数组(性能较差)
3. 事件委托
javascript
// 父元素监听,子元素触发
parentElement.addEventListener('click', (e) => {
if (e.target.matches('.child-element')) {
// 处理子元素点击事件
}
});
七、调试与错误处理
1. 控制台美化输出
javascript
// 带颜色的日志
console.log('%c重要信息', 'color: blue; font-weight: bold');
// 表格形式输出
console.table([{ name: '张三', age: 20 }, { name: '李四', age: 25 }]);
2. 错误边界(Error Boundary)
javascript
// 自定义错误捕获函数
window.onerror = function(message, source, lineno, colno, error) {
// 记录错误信息
logError({ message, source, lineno, colno, error });
return true; // 阻止错误冒泡
};
3. 断言(Assertion)
javascript
function assert(condition, message) {
if (!condition) {
throw new Error(message || 'Assertion failed');
}
}
// 使用示例
assert(typeof value === 'number', 'value必须是数字');
八、类型检查与转换
1. 类型安全检查
javascript
// 检查数组
Array.isArray([]); // true
// 检查空对象
const isEmptyObject = obj =>
obj && typeof obj === 'object' && !Object.keys(obj).length;
// 检查null/undefined
const isNullOrUndefined = val => val == null; // 注意:使用==而非===
2. 安全的类型转换
javascript
// 字符串转数字
const num = parseInt('123', 10); // 第二个参数必须为10
// 安全的JSON解析
const parseJSON = (str) => {
try {
return JSON.parse(str);
} catch (e) {
return null;
}
};
十四、Cypress持续集成实践——让测试融入开发流水线
vueuse的createReusableTemplate函数实现原理
使用vueuse的时候发现里面有一个createReusableTemplate
的工具函数很有意思
根据文档的介绍,它的使用方式如下:
开发中也许会经常遇到这种情况
<template>
<dialog v-if="showInDialog">
<!-- something complex -->
</dialog>
<div v-else>
<!-- something complex -->
</div>
</template>
我们希望代码尽可能的重用,通常情况下我们会将<!-- something complex -->
抽取成组件,但是那样做,在抽取出去的组件中,你就没法访问到当前SFC中的各种变量,而只能通过定义props
、emits
来实现数据传递,这样会有些繁琐
createReuseableTemplate
能够在当前组件的template中直接定义一个可重用的片段,就像这样:
<script setup>
import { createReusableTemplate } from '@vueuse/core'
const [DefineTemplate, ReuseTemplate] = createReusableTemplate()
</script>
<template>
<DefineTemplate>
<!-- something complex -->
</DefineTemplate>
<dialog v-if="showInDialog">
<ReuseTemplate />
</dialog>
<div v-else>
<ReuseTemplate />
</div>
</template>
在这个示例中,<DefineTemplate>
中写的代码将不会渲染任何东西,而会渲染在<ReuseTemplate />
处
不禁惊叹这是什么黑科技,迫切想知道是怎么实现的,于是研究了一下源码
其实实现原理很简单,稍微拆分一下它的代码并手动实现如下:
<script setup>
import { defineComponent } from 'vue';
import { ref } from 'vue'
const msg = ref('Hello World!')
// 首先是可重用的template的定义
// 其实它就是一个Component
const DefineTmpl = defineComponent({
setup(_, { slots }) {
// 拿到默认的插槽,console.log打印一下就能看出来,这玩意实际上就是一个渲染函数
// 而setup方法,是可以返回一个渲染函数的(也就是一个能返回vnode的函数),具体可看vue官方文档:
// https://cn.vuejs.org/guide/extras/render-function.html#declaring-render-function
return () => slots.default()
// 这里甚至可以直接return默认插槽,因为它本身就是一个函数
// return slots.default
}
})
</script>
<template>
<DefineTmpl>
<h1>{{ msg }}</h1>
<input v-model="msg" />
</DefineTmpl>
</template>
以上代码的结果就是:你在DefineTmpl
写的任何东西(其实就是默认插槽的内容)都会被原样渲染出来
其实也就是跟不存在DefineTmpl
几乎没有区别:
<script setup>
import { defineComponent } from 'vue';
import { ref } from 'vue'
const msg = ref('Hello World!')
</script>
<template>
<h1>{{ msg }}</h1>
<input v-model="msg" />
</template>
那么DefineTmpl
存在的意义其实就是拿到默认插槽的内容(也就是你在DefineTmpl
里写的任何html),它是一个渲染函数,那就用一个变量来接收它
然后该怎么重用它呢??也就是再定义一个Component,返回DefineTmpl
里获取到的默认插槽内容就行了:
<script setup>
import { defineComponent } from 'vue';
import { ref } from 'vue'
const msg = ref('Hello World!')
let renderFn;
const DefineTmpl = defineComponent({
setup(_, { slots }) {
renderFn = slots.default
}
})
const Reuse = defineComponent({
setup() {
if (!renderFn) return console.warn('未定义模板!')
return renderFn
// 或者 return () => renderFn()
}
})
</script>
<template>
<p>DefineTmpl部分(不会渲染任何东西)</p>
<DefineTmpl>
<h1>{{ msg }}</h1>
<input v-model="msg" />
</DefineTmpl>
<h2>================分割线================</h2>
<Reuse />
</template>
然后在页面任意地方写上就可以了,渲染结果:
如果想传入一些自定义的东西怎么办呢,稍微改造一下Reuse:
const Reuse = defineComponent({
setup(_, { attrs }) {
if (!renderFn) {
console.warn('未定义模板')
}
return () => renderFn({ ...attrs }) // 将attrs作为渲染函数的props传入
}
})
然后在DefineTmpl
部分可以这样写:
<template>
<p>DefineTmpl部分(不会渲染任何东西)</p>
<DefineTmpl #default="{ data }">
<h1>{{ msg }}</h1>
<input v-model="msg" />
<p>自定义内容:{{ data }}</p>
</DefineTmpl>
<h2>================分割线================</h2>
<Reuse :data="'我是自定义内容'" />
</template>
渲染结果:
整个封装一下,就是一个简陋版本的createReusableTemplate
了(当然比vueuse中简陋很多,不过差不多核心实现原理也就是这样了)
function createMyReusableTemplate() {
let renderFn;
const DefineTmpl = defineComponent({
setup(_, { slots }) {
renderFn = slots.default
}
})
const Reuse = defineComponent({
setup(_, { attrs }) {
if (!renderFn) {
console.warn('未定义模板')
}
return () => renderFn({ ...attrs })
}
})
return [DefineTmpl, Reuse]
}
useContext 与 useReducer 的组合使用
前言
在讲useReducer之前需要介绍一下useState这个hooks函数。useState用于数据管理,响应式数据管理,允许我们在函数组件中存储状态。
随着应用逐渐复杂,我们经常发现useState
在管理复杂的状态逻辑时显得有些力不从心。这时,React为我们提供的另一个更为强大的hook——useReducer
——可以帮助我们优雅地处理复杂状态。
useReducer
允许我们使用 action 和 reducer 的方式来组织复杂的状态逻辑,使其变得更加清晰和模块化,弥补了useState
的局限性。
今天让我们用useReducer
+useContext
来实现一个todosList功能。
了解一下什么是useReducer
?
useReducer
与useState
相似,useReducer
也是 React 的 Hook,而且也只能放在组件最顶层使用。与前者不同的地方在于,它是通过 action 来更新状态的,使状态更新逻辑更具可读性。
useReducer
接受三个参数:
-
reducer
:用于更新 state 的纯函数。参数为 state 和 action,返回值是更新后的 state。state 与 action 可以是任意合法值。 -
initialArg
:用于初始化 state 的任意值。初始值的计算逻辑取决于接下来的init
参数。 -
可选参数
init
:用于计算初始值的函数。如果存在,使用init(initialArg)
的执行结果作为初始值,否则使用initialArg
。
什么是纯函数?
纯函数(Pure Function)是函数式编程中的一个核心概念,它指的是那些在同样的输入下总是产生相同输出的函数,并且不会产生任何副作用。具体来说,纯函数具有以下两个主要特性:
- 确定性:给定相同的输入参数,纯函数总是返回相同的结果。这意味着纯函数的输出仅取决于它的输入参数,不受外部状态(比如全局变量、数据库查询或网络请求等)的影响。
- 无副作用:纯函数不会修改任何外部状态或数据,也不会进行任何形式的I/O操作,例如文件系统操作、网络请求或打印到控制台等。它们仅仅根据输入参数计算并返回结果。
useReducer
返回两个参数:
useReducer
返回一个由两个值组成的数组:
- 当前的 state。初次渲染时,它是
init(initialArg)
或initialArg
(如果没有init
函数)。 -
dispatch
函数。用于更新 state 并触发组件的重新渲染。
const [state, dispatch] = useReducer(reducer, initialArg, init?)
当你使用 useReducer
时,useReducer
返回一个数组,第一个元素是当前的状态 (state
),第二个元素是一个可以用来触发状态更新的 dispatch
函数。通过解构赋值,我们可以方便地将这两个值分别赋给 state
和 dispatch
变量。
实现todoList
1.创建全局上下文
export const TodoContext = createContext(null);
2.创建自定义hooks函数useTodos
,其主要作用是封装待办事项(todos)的状态管理和操作逻辑,方便在不同组件中复用。
const initialTodos =[
{
id:1,
text:'学习React',
done:false
}
]
export function useTodos(initial=initialTodos) {
const [todos,dispatch] = useReducer(todoReducer,initial)
const addTodo = text => dispatch({type: 'ADD_TODO',text})
const toggleTodo =(id)=> dispatch({type: 'TOGGLE_TODO',id})
const removeTodo =(id)=> dispatch({type: 'REMOVE_TODO',id})
return {
todos,
addTodo,
toggleTodo,
removeTodo
}
}
3.在App.jsx
中,用创建好的上下文包裹子组件,这样就不需要传递数据了,value
属性指定了要共享的数据,这里共享的是 todosHook
对象。todosHook
包含当前的待办事项列表 todos 和三个操作函数 addTodo 、 toggleTodo 、 removeTodo 。
function App() {
const todosHook=useTodos();
return (
<TodoContext.Provider value={todosHook}>
<h1>Todo App</h1>
<AddTodo />
<TodoList />
</TodoContext.Provider>
)
}
4.创建一个纯函数,用来出来todoReducer
用于根据不同的 action 来更新待办事项的状态。reducer 接收两个参数:
- state :当前的待办事项状态,通常是一个数组。
- action :一个对象,包含 type 属性(表示操作类型)和其他必要的数据。
function todoReducer(state,action){
switch(action.type){
case 'ADD_TODO':
return [...state,{
id:Date.now(),
text:action.text,
done:false
}]
case 'TOGGLE_TODO':
return state.map(todo =>
todo.id === action.id ?{...todo,done:!todo.done}:todo
);
case 'REMOVE_TODO':
return state.filter(todo => todo.id !== action.id);
default:
return state;
}
}
5.创建一个自定义hooksuseTodoContext
,使用全局上下文.创建这么多自定义hooks函数,就是为了让组件内部更加干净,更好管理。
export function useTodoContext(){
return useContext(TodoContext);
}
6.完成添加todo的功能
const AddTodo =()=>{
const [text,setText]=useState('');
const { addTodo }=useTodoContext();// 跨层级
const handleSubmit=(e)=>{
e.preventDefault(); // 阻止表单默认行为
if(text.trim()){ // 去空格
addTodo(text.trim()); // 调用addTodo方法添加任务
setText(''); // 清空输入框
}
}
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={text}
onChange={(e)=>{
setText(e.target.value)
}}
/>
<button type="submit">Add</button>
</form>
)
}
7.完成展示todoslist的功能
const TodoList = () => {
const {
todos,
toggleTodo,
removeTodo,
} = useTodoContext();
return (
<ul>
{
todos.map((todo)=>(
<li key={todo.id}>
<span
onClick={()=>toggleTodo(todo.id)}
style={{textDecoration:todo.done ? 'line-through':'none'}}
>
{todo.text}
{/* {localStorage.getItem('text')} */}
</span>
<button onClick={()=>removeTodo(todo.id)}>remove</button>
</li>
))
}
</ul>
)
}
总结
- useReducer 核心 :包含响应式状态管理、使用纯函数 reducer 规定状态改变规则、初始值 initValue 以及通过 dispatch 派发 action 对象。
- useContext 作用 :用于跨层次共享状态,借助 createContext 创建上下文, Context.Provider 提供状态, useContext 消费状态。
- 组合使用 :将 useContext 和 useReducer 结合,可实现跨层级的全局状态管理,应用场景包括主题、登录状态、待办事项等。
- 自定义 Hook :介绍了组件渲染与 Hook 状态管理的关系,以及 hook 分别与 useContext 、 useReducer 组合使用在全局应用级别状态管理中的作用。