第6期 - 小桥流水
封面图来源于苏州平江路,感受真正的小桥流水人家。
寒山寺
技术分享-hook相关
useContext、useState、use
useContext 跨组件深层次传值, 读取和订阅组件中的 context use 读取类似于 Promise 或 context 的资源的值。会在数据获取到后重新渲染组件, 可在条件语句中调用。
useContext: React 是单向数据流,如果组件之间有嵌套引用的关系如 A 引用 B,B 引用 C,像套娃一样的关系, 在没有状态管理的情况下,只能通过 props 一层一层的进行传递。实现组件值的传递,不需要经过中间组件 Context 允许父组件向其下层无论多深的任何组件提供信息,而无需通过 props 显式传递。总是在调用它的组件 上面 寻找最近的 provider
import { createContext, useContext } from 'react';
const ThemeContext = createContext(null);
// 在 MyApp 顶层包装了 ThemeContext.Provider 使得 Form 内可以使用 ThemeContext 值
export default function MyApp() {
return (
<ThemeContext.Provider value="dark">
<Form />
</ThemeContext.Provider>
)
}
// 更改 Context 值
const ThemeContext = createContext('light');
export default function MyApp() {
const [theme, setTheme] = useState('light');
return (
<>
<ThemeContext.Provider value={theme}>
<Form />
</ThemeContext.Provider>
<Button onClick={() => {
setTheme(theme === 'dark' ? 'light' : 'dark');
}}>
Toggle theme
</Button>
</>
)
}
use:
- 使用 use 读取 context, 即异步组件内容的读取
- 将数据从服务器流式传递给客户端
"use client";
import { use, Suspense } from "react";
const ThemeContext = createContext(null); // 模拟假设组件内容
function Panel({ title, children }) {
const theme = use(ThemeContext);
const className = 'panel-' + theme;
return (
<section className={className}>
<h1>{title}</h1>
{children}
</section>
)
}
function Message({ messagePromise }) {
const messageContent = use(messagePromise);
return <p>Here is the message: {messageContent}</p>;
}
export function MessageContainer({ messagePromise }) {
return (
<Suspense fallback={<p>⌛Downloading message...</p>}>
<Message messagePromise={messagePromise} />
</Suspense>
);
}
useEffect
useEffect 返回 undefined, 只能在 组件的顶层 或自己的 Hook 中调用。返回一个 清理函数,初次挂载会执行内含的函数,当依赖项发生变化时,先执行 return 的内容进行上一次的清理,再执行内含的函数
import { useEffect } from 'react';
import { createConnection } from './chat.js';
function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');
useEffect(() => {
// 内含的函数
const connection = createConnection(serverUrl, roomId);
connection.connect();
// 清理函数
return () => {
connection.disconnect();
};
}, [serverUrl, roomId]); // 依赖项
}
useMemo、useCallback
useMemo 缓存定义的函数计算的结果,当依赖项没有发生变化则取上一次值,变化则重新执行 useCallback 缓存函数。返回一个函数,依赖发生变化执行传入函数,否则拿到上一次传入的函数缓存
useCallback 用法意义:
- 将其作为 props 传递给包装在 [memo] 中的组件。如果 props 未更改,则希望跳过重新渲染。缓存允许组件仅在依赖项更改时重新渲染。
- 传递的函数可能作为某些 Hook 的依赖
- 避免不必要地更新 Effect
import { useMemo, useCallback } from 'react';
function ProductPage({ productId, referrer }) {
const product = useData('/product/' + productId);
const requirements = useMemo(() => { //调用函数并缓存结果
return computeRequirements(product);
}, [product]);
const handleSubmit = useCallback((orderDetails) => { // 缓存函数本身,避免重复渲染
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}, [productId, referrer]);
// 与 useEffect 套用
const createOptions = useCallback(() => {
return {
serverUrl: 'https://localhost:1234',
roomId: roomId
};
}, [roomId]); // ✅ 仅当 roomId 更改时更改
useEffect(() => {
const options = createOptions();
const connection = createConnection();
connection.connect();
return () => connection.disconnect();
}, [createOptions]); // ✅ 仅当 createOptions 更改时更改
return (
<div className={theme}>
<ShippingForm requirements={requirements} onSubmit={handleSubmit} />
</div>
);
}
useDeferredValue、useLayoutEffect
useDeferredValue 延迟更新 UI,中断重新渲染,如果 value 有新的更新,React 会从头开始重新启动后台渲染。即 在数据加载完成前,页面 UI 暂停更新,将会使用原来的旧值(如:用户在输入框中的输入速度比接收延迟值的图表重新渲染的速度快,那么图表只会在用户停止输入后重新渲染)
useLayoutEffect 在浏览器重新绘制屏幕之前进行布局测量
import { Suspense, useState, useDeferredValue } from 'react';
import SearchResults from './SearchResults.js';
const ref = useRef(null);
const [tooltipHeight, setTooltipHeight] = useState(0);
useLayoutEffect(() => {
const { height } = ref.current.getBoundingClientRect();
setTooltipHeight(height);
console.log('Measured tooltip height: ' + height);
}, []);
export default function App() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
return (
<>
<label>
Search albums:
<input value={query} onChange={e => setQuery(e.target.value)} />
</label>
<Suspense fallback={<h2>Loading...</h2>}>
<SearchResults query={deferredQuery} />
</Suspense>
</>
);
}
与防抖节流有什么区别? 防抖:用户停止输入一段时间后更新 节流:每隔一段时间进行更新 实际上,我认为没太大区别,不过在 react 项目中,可以直接使用进行优化。上手简单,开箱即用
自定义 hook
理解为抽取一个公共的函数方法,在需要的地方进行调用即可。自定义 Hook 共享的只是状态逻辑而不是状态本身。对 Hook 的每个调用完全独立于对同一个 Hook 的其他调用
function useChatRoom({ serverUrl, roomId }) {
useEffect(() => {
const options = {
serverUrl: serverUrl,
roomId: roomId
};
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [roomId, serverUrl]);
}
function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');
useChatRoom({
roomId: roomId,
serverUrl: serverUrl
});
return (
<>
<label>
Server URL:{' '}
<input
value={serverUrl}
onChange={e => setServerUrl(e.target.value)}
/>
</label>
<h1>Welcome to the {roomId} room!</h1>
</>
}
useRef
import { useRef } from 'react';
export default function Form() {
const inputRef = useRef(null);
let ref = useRef(0); // 缓存值
// 获取dom对应的ref进行操作
function handleClick() {
inputRef.current.focus();
}
function handleClick2() {
ref.current = ref.current + 1;
alert('You clicked ' + ref.current + ' times!');
}
return (
<>
<input ref={inputRef} />
<button onClick={handleClick}>
Focus the input
</button>
<button onClick={handleClick2}>
Click me!
</button>
</>
);
}
// 避免创建重复
function Video() {
const playerRef = useRef(null);
if (playerRef.current === null) {
playerRef.current = new VideoPlayer();
}