React notes
reducer action type definition
在 React 中使用 useReducer 时,可以使用 TypeScript 来标注 action 的类型。下面是一个示例:
import { useReducer } from 'react';
type State = {
count: number;
};
type Action =
| { type: 'increment'; payload: number }
| { type: 'decrement'; payload: number };
const initialState: State = {
count: 0,
};
const reducer = (state: State, action: Action): State => {
switch (action.type) {
case 'increment':
return { count: state.count + action.payload };
case 'decrement':
return { count: state.count - action.payload };
default:
throw new Error('Unknown action type');
}
};
const Counter = () => {
const [state, dispatch] = useReducer(reducer, initialState);
const handleIncrement = () => {
dispatch({ type: 'increment', payload: 1 });
};
const handleDecrement = () => {
dispatch({ type: 'decrement', payload: 1 });
};
return (
<div>
<button onClick={handleIncrement}>Increment</button>
<button onClick={handleDecrement}>Decrement</button>
<p>Count: {state.count}</p>
</div>
);
};
在上述示例中,我们定义了 Action 类型,它是一个联合类型,包含了所有可能的 action 类型。每个具体的 action 都需要包含 type 属性来指示其类型,以及其他可选的 payload 属性来携带额外的数据。
在 reducer 函数中,我们对 action 进行了类型标注,确保只处理已定义的 action 类型。如果出现未知的 action 类型,我们抛出一个错误。
在组件中,我们使用 useReducer 来创建状态 state 和 dispatch 函数。我们定义了两个处理函数 handleIncrement 和 handleDecrement,并在调用 dispatch 时传递了相应的 action。
这样,在使用 useReducer 的过程中,你就可以使用 TypeScript 来明确标注 action 的类型,以提供更好的类型检查和类型推断功能。
useContext with state
在 React 中,可以使用状态(state)来管理 Context。一种常见的做法是使用 Context 提供器(Context Provider)组件,并在其内部使用状态来跟踪和更新提供的值。这样,通过 Context,可以在组件树中的其他组件中访问和更新该状态。
下面是一个示例,展示了如何在 Context 中使用状态:
import React, { createContext, useState } from 'react';
// 创建一个 Context
const MyContext = createContext();
// 创建 Context 提供器组件
const MyContextProvider = ({ children }) => {
const [value, setValue] = useState('Initial Value');
const updateValue = (newValue) => {
setValue(newValue);
};
return (
<MyContext.Provider value={{ value, updateValue }}>
{children}
</MyContext.Provider>
);
};
// 使用 Context 提供器包裹组件树
const App = () => {
return (
<MyContextProvider>
<ChildComponent />
</MyContextProvider>
);
};
// 在子组件中使用 Context
const ChildComponent = () => {
const { value, updateValue } = useContext(MyContext);
const handleButtonClick = () => {
updateValue('New Value');
};
return (
<div>
<p>Value: {value}</p>
<button onClick={handleButtonClick}>Update Value</button>
</div>
);
};
在上述示例中,我们创建了一个名为 MyContext 的 Context,并使用 createContext 函数进行创建。然后,我们创建了 MyContextProvider 组件作为 Context 提供器,它使用 useState 钩子来跟踪和更新状态 value。updateValue 函数用于更新状态的值。
在 MyContextProvider 组件内部,我们使用 MyContext.Provider 组件来提供状态值 value 和更新函数 updateValue 给其后代组件。通过 value 属性,可以将状态值传递给 ChildComponent 组件。
在 ChildComponent 中,我们使用 useContext 钩子来访问 Context 的值,获取到了 value 和 updateValue。当点击按钮时,调用 updateValue 更新状态值。
这样,通过在提供器组件中使用状态来管理 Context,可以在组件树中的其他组件中访问和更新状态值。注意,在实际应用中,可以根据需要在 Context 中提供更多的状态和函数。
ref callback function
zh-hans.react.dev/learn/manip…
<li
key={cat.id}
ref={(node) => {
const map = getMap();
if (node) {
map.set(cat.id, node);
} else {
map.delete(cat.id);
}
}}
>
在 React 中,ref 是用于访问组件或 DOM 元素的引用。在你提供的代码片段中,ref 是一个函数,它接收一个参数 node,表示对 DOM 元素的引用。
当 React 渲染组件时,会调用 ref 函数两次:
- 第一次调用是在组件挂载到 DOM 树上时。此时
node参数会被传递一个有效的 DOM 元素,表示该组件已经被挂载到 DOM 上。 - 第二次调用是在组件从 DOM 树中卸载时。此时
node参数会被传递null,表示该组件已经从 DOM 上卸载。
在你的代码中,你利用 ref 函数来维护一个 Map,将 cat.id 和对应的 DOM 元素关联起来。当组件被挂载到 DOM 上时,会将 DOM 元素添加到 Map 中;当组件从 DOM 上卸载时,会从 Map 中删除对应的条目。
需要注意的是,在 React 的生命周期中,组件被卸载时会先调用 ref 函数传递 null,然后再执行其他的卸载操作。因此,ref 函数中的 if (node) 分支表示在组件卸载时会将对应的条目从 Map 中删除。
所以,不会出现没有 node 的情况,因为 React 会确保在卸载组件之前将 ref 函数调用一次并传递 null。这样可以保证在 Map 中正确地添加和删除条目,确保数据的一致性。
useReducer & Context (simple Redux)
在 React 中可以使用自带的 useReducer 和 Context API 来实现 Redux 的基本功能。虽然 Redux 是一个专门的状态管理库,但是基本原理可以在 React 中使用这些原生的功能来实现。
以下是一个简单示例,展示如何使用 useReducer 和 Context 实现 Redux 的基本功能:
首先,创建一个 store.js 文件,定义初始状态和操作函数:
import React, { createContext, useReducer } from 'react';
// 初始状态
const initialState = {
count: 0,
};
// 操作类型
const ActionTypes = {
INCREMENT: 'INCREMENT',
DECREMENT: 'DECREMENT',
};
// 操作函数
const reducer = (state, action) => {
switch (action.type) {
case ActionTypes.INCREMENT:
return { count: state.count + 1 };
case ActionTypes.DECREMENT:
return { count: state.count - 1 };
default:
return state;
}
};
// 创建 Context
const StoreContext = createContext();
// 创建 Provider
const StoreProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<StoreContext.Provider value={{ state, dispatch }}>
{children}
</StoreContext.Provider>
);
};
export { ActionTypes, StoreContext, StoreProvider };
然后,在应用程序的顶层组件中使用 StoreProvider 包裹组件,并通过 StoreContext.Provider 提供状态和操作函数:
import React from 'react';
import { StoreProvider } from './store';
const App = () => {
return (
<StoreProvider>
{/* 应用程序的其他组件 */}
</StoreProvider>
);
};
export default App;
接下来,在需要访问状态或触发操作的组件中使用 StoreContext.Consumer 或 useContext 来获取状态和操作函数:
import React, { useContext } from 'react';
import { ActionTypes, StoreContext } from './store';
const Counter = () => {
const { state, dispatch } = useContext(StoreContext);
const handleIncrement = () => {
dispatch({ type: ActionTypes.INCREMENT });
};
const handleDecrement = () => {
dispatch({ type: ActionTypes.DECREMENT });
};
return (
<div>
<p>Count: {state.count}</p>
<button onClick={handleIncrement}>Increment</button>
<button onClick={handleDecrement}>Decrement</button>
</div>
);
};
export default Counter;
通过使用 useReducer 来管理状态,然后使用 Context 提供状态和操作函数,可以在 React 中实现类似 Redux 的基本功能。这种方式更适合简单的应用程序,而对于复杂的状态管理和高级功能,Redux 仍然是一个强大的选择。
Context with async request
使用一个全局上下文(context)来存储数据,并在 useEffect 中调用请求并设置该上下文是一种有效的方法。这种方法可以使数据在应用程序中的多个组件之间共享,并确保数据的一致性。 以下是一个示例,展示如何使用全局上下文和 useEffect 来调用异步请求并设置上下文中的数据:
import React, { createContext, useEffect, useState } from 'react';
const DataContext = createContext();
const DataProvider = ({ children }) => {
const [data, setData] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
setData(data);
} catch (error) {
console.error('Error fetching data:', error);
}
};
fetchData();
}, []);
return (
<DataContext.Provider value={data}>
{children}
</DataContext.Provider>
);
};
export { DataContext, DataProvider };
在上述示例中,我们创建了一个名为DataContext的全局上下文,并使用useState来存储数据。在useEffect中,我们调用异步请求来获取数据,并使用setData方法设置上下文中的数据。
DataProvider组件将上下文的值设置为当前数据,并将其作为value属性传递给DataContext.Provider。这样,订阅该上下文的所有组件都可以访问到最新的数据。
在其他组件中,可以使用useContext钩子来访问上下文中的数据:
import React, { useContext } from 'react';
import { DataContext } from './DataContext';
const MyComponent = () => {
const data = useContext(DataContext);
return (
<div>
{data ? (
<ul>
{data.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
) : (
<p>Loading...</p>
)}
</div>
);
};
在上述示例中,我们使用useContext钩子从DataContext中获取数据,并根据数据是否存在来渲染组件。
通过使用全局上下文和useEffect,可以在应用程序中的任何组件中获取和共享数据,并确保数据的一致性。这种方法非常适用于需要在多个组件之间共享数据的大型应用程序。
React lazy router
React Router 提供了多种路由类型,包括异步路由加载。
异步路由加载可以帮助优化应用程序的性能,特别是在应用程序包含大量页面或组件时。通过按需加载路由组件,可以在需要时动态加载页面,减少初始加载时间并提高用户体验。
下面是使用 React Router v6 中的异步路由加载的示例:
import React, { lazy, Suspense } from 'react';
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
// 使用 React.lazy 创建异步加载的组件
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const App = () => {
return (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Suspense>
</Router>
);
};
export default App;
在上述示例中,我们使用 React.lazy 函数来创建异步加载的组件。通过调用 import() 函数并传递组件的路径,可以动态地加载组件。
在路由配置中,我们使用 <Routes> 组件来定义路由,并使用 <Route> 组件来指定路由的路径和要渲染的组件。在组件属性中使用 element 来指定要渲染的异步加载组件。
通过使用 <Suspense> 组件包裹 <Routes>,我们可以提供一个加载中的占位符,当异步组件加载时显示。在上述示例中,我们使用 <div>Loading...</div> 作为加载中的占位符,但你可以根据需要自定义。
请注意,上述示例使用了 React Router v6 版本的语法。如果你使用的是 React Router v5 或更早版本,可以将 Suspense 和 Routes 替换为 Switch 和 Route。
使用异步路由加载可以根据需要加载组件,提高应用程序的性能和用户体验。当用户访问特定的路由时,相应的组件会在需要时动态加载,并且在加载过程中显示适当的加载中状态。
转载自:https://juejin.cn/post/7249020485471010876