React API 参考
本文档提供了 React 中关键 API 的使用说明和示例,重点关注性能优化相关的 API。
createContext
createContext
用于创建一个上下文(Context),使组件树中的组件可以共享数据,而不必通过 props 层层传递。
基本用法
tsx
import React, { hooks, createContext } from "@dao3fun/react";
const { useContext, useState } = hooks;
// 创建上下文
const ThemeContext = createContext("light");
// 子组件通过useContext访问上下文
function ThemedButton() {
// 使用上下文中的值
const theme = useContext(ThemeContext);
return (
<box
style={{
backgroundColor:
theme === "dark"
? Vec3.create({ r: 50, g: 50, b: 50 })
: Vec3.create({ r: 240, g: 240, b: 240 }),
size: { offset: Vec2.create({ x: 120, y: 40 }) },
}}
>
<text
style={{
textColor:
theme === "dark"
? Vec3.create({ r: 255, g: 255, b: 255 })
: Vec3.create({ r: 50, g: 50, b: 50 }),
}}
>
当前主题: {theme}
</text>
</box>
);
}
// 中间组件,不需要关心theme
function Toolbar() {
return (
<box>
<ThemedButton />
</box>
);
}
// 根组件提供上下文值
function App() {
const [theme, setTheme] = useState("light");
return (
<ThemeContext.Provider value={theme}>
<box>
<Toolbar />
<box
style={{
backgroundColor: Vec3.create({ r: 0, g: 122, b: 255 }),
size: { offset: Vec2.create({ x: 150, y: 40 }) },
position: { offset: Vec2.create({ x: 0, y: 50 }) },
}}
onClick={() => setTheme(theme === "light" ? "dark" : "light")}
>
<text style={{ textColor: Vec3.create({ r: 255, g: 255, b: 255 }) }}>
切换主题
</text>
</box>
</box>
</ThemeContext.Provider>
);
}
提供默认值
创建上下文时可以提供默认值,当组件树中没有匹配的 Provider 时使用:
tsx
// 设置默认主题为"light"
const ThemeContext = createContext("light");
嵌套 Provider
Context Provider 可以嵌套使用,内层 Provider 会覆盖外层的值:
tsx
function NestedProviders() {
return (
<ThemeContext.Provider value="dark">
<box>
{/* 这里的组件会接收到"dark"主题 */}
<ThemedButton />
<ThemeContext.Provider value="light">
{/* 这里的组件会接收到"light"主题 */}
<ThemedButton />
</ThemeContext.Provider>
</box>
</ThemeContext.Provider>
);
}
最佳实践
- 避免过度使用:只用于确实需要在组件树多个层级共享的数据
- 拆分上下文:不同类型的数据使用不同的 Context
- 考虑性能:Context 值变化会导致所有消费它的组件重新渲染
forwardRef
forwardRef
允许组件接收父组件传递的 ref,并将其转发到子组件中的 DOM 元素或组件实例。
基本用法
tsx
import React, { hooks, forwardRef } from "@dao3fun/react";
const { useRef } = hooks;
// 使用forwardRef创建可转发ref的组件
const CustomInput = forwardRef((props, ref) => {
return (
<input
ref={ref}
style={{
size: { offset: Vec2.create({ x: 200, y: 40 }) },
backgroundColor: Vec3.create({ r: 245, g: 245, b: 245 }),
}}
onBlur={(e) => props.onUpdate && props.onUpdate(e.target.textContent)}
>
{props.defaultValue || ""}
</input>
);
});
// 使用带有ref的组件
function Form() {
const inputRef = useRef(null);
const focusInput = () => {
// 可以直接访问input元素
if (inputRef.current) {
inputRef.current.focus();
}
};
return (
<box>
<CustomInput ref={inputRef} defaultValue="请输入内容" />
<box
style={{
backgroundColor: Vec3.create({ r: 0, g: 122, b: 255 }),
size: { offset: Vec2.create({ x: 120, y: 40 }) },
position: { offset: Vec2.create({ x: 0, y: 50 }) },
}}
onClick={focusInput}
>
<text style={{ textColor: Vec3.create({ r: 255, g: 255, b: 255 }) }}>
聚焦输入框
</text>
</box>
</box>
);
}
与 useImperativeHandle 结合
结合 useImperativeHandle
可以自定义暴露给父组件的实例值:
tsx
import React, { hooks, forwardRef } from "@dao3fun/react";
const { useImperativeHandle, useRef, useState } = hooks;
// 定义组件实例接口
interface InputHandles {
focus: () => void;
clear: () => void;
getValue: () => string;
}
// 创建自定义操作的输入框
const CustomInput = forwardRef<InputHandles>((props, ref) => {
const [value, setValue] = useState(props.defaultValue || "");
const inputRef = useRef(null);
// 自定义暴露的方法
useImperativeHandle(
ref,
() => ({
focus: () => {
inputRef.current?.focus();
},
clear: () => {
setValue("");
},
getValue: () => {
return value;
},
}),
[value]
);
return (
<input
ref={inputRef}
style={{
size: { offset: Vec2.create({ x: 200, y: 40 }) },
backgroundColor: Vec3.create({ r: 245, g: 245, b: 245 }),
}}
onBlur={(e) => setValue(e.target.textContent)}
>
{value}
</input>
);
});
// 使用带有增强功能的组件
function EnhancedForm() {
const inputRef = useRef<InputHandles>(null);
const handleClear = () => {
inputRef.current?.clear();
};
const handleGetValue = () => {
const value = inputRef.current?.getValue() || "";
console.log("当前值:", value);
};
return (
<box>
<CustomInput ref={inputRef} defaultValue="请输入内容" />
<box style={{ position: { offset: Vec2.create({ x: 0, y: 50 }) } }}>
<box
style={{
backgroundColor: Vec3.create({ r: 255, g: 59, b: 48 }),
size: { offset: Vec2.create({ x: 100, y: 40 }) },
}}
onClick={handleClear}
>
<text style={{ textColor: Vec3.create({ r: 255, g: 255, b: 255 }) }}>
清空
</text>
</box>
<box
style={{
backgroundColor: Vec3.create({ r: 0, g: 122, b: 255 }),
size: { offset: Vec2.create({ x: 100, y: 40 }) },
position: { offset: Vec2.create({ x: 110, y: 0 }) },
}}
onClick={handleGetValue}
>
<text style={{ textColor: Vec3.create({ r: 255, g: 255, b: 255 }) }}>
获取值
</text>
</box>
</box>
</box>
);
}
使用场景
- 访问 DOM 元素:需要直接操作组件内部的 DOM 元素
- 构建可复用组件库:为组件用户提供命令式 API
- 第三方集成:与需要引用的第三方库集成
startTransition
startTransition
用于标记非紧急的状态更新,使 UI 保持响应性,即使在大型更新期间也能接收用户输入。
基本用法
tsx
import React, { hooks, startTransition } from "@dao3fun/react";
const { useState } = hooks;
function TabsExample() {
const [tab, setTab] = useState("home");
const [isPending, setIsPending] = useState(false);
// 模拟大量内容
const tabContent = {
home: Array.from({ length: 500 }, (_, i) => `首页内容项 ${i + 1}`),
about: Array.from({ length: 500 }, (_, i) => `关于内容项 ${i + 1}`),
contact: Array.from({ length: 500 }, (_, i) => `联系内容项 ${i + 1}`),
};
const handleTabChange = (newTab) => {
// 标记为过渡更新
setIsPending(true);
startTransition(() => {
setTab(newTab);
setIsPending(false);
});
};
return (
<box
style={{
backgroundColor: Vec3.create({ r: 245, g: 245, b: 245 }),
size: { offset: Vec2.create({ x: 500, y: 500 }) },
}}
>
{/* 标签栏 */}
<box
style={{
position: { offset: Vec2.create({ x: 20, y: 20 }) },
size: { offset: Vec2.create({ x: 460, y: 50 }) },
backgroundColor: Vec3.create({ r: 255, g: 255, b: 255 }),
}}
>
{["home", "about", "contact"].map((tabName) => (
<box
style={{
backgroundColor:
tab === tabName
? Vec3.create({ r: 33, g: 150, b: 243 })
: Vec3.create({ r: 224, g: 224, b: 224 }),
position: {
offset: Vec2.create({
x: ["home", "about", "contact"].indexOf(tabName) * 150 + 10,
y: 10,
}),
},
size: { offset: Vec2.create({ x: 140, y: 30 }) },
}}
onClick={() => handleTabChange(tabName)}
>
<text>
{tabName === "home"
? "首页"
: tabName === "about"
? "关于"
: "联系"}
</text>
</box>
))}
</box>
{/* 加载指示器 */}
{isPending && (
<text
style={{
position: { offset: Vec2.create({ x: 20, y: 80 }) },
textColor: Vec3.create({ r: 150, g: 150, b: 150 }),
}}
>
加载中...
</text>
)}
{/* 内容区域 */}
<box
style={{
position: { offset: Vec2.create({ x: 20, y: 100 }) },
size: { offset: Vec2.create({ x: 460, y: 380 }) },
backgroundColor: Vec3.create({ r: 255, g: 255, b: 255 }),
backgroundOpacity: isPending ? 0.7 : 1,
}}
>
<box
style={{
position: { offset: Vec2.create({ x: 10, y: 10 }) },
size: { offset: Vec2.create({ x: 440, y: 360 }) },
}}
>
{/* 显示部分内容项 */}
{tabContent[tab].slice(0, 10).map((item, index) => (
<text
style={{
position: { offset: Vec2.create({ x: 0, y: index * 30 }) },
textColor: isPending
? Vec3.create({ r: 180, g: 180, b: 180 })
: Vec3.create({ r: 50, g: 50, b: 50 }),
}}
>
{item}
</text>
))}
{/* 显示剩余项目数量 */}
{tabContent[tab].length > 10 && (
<text
style={{
position: { offset: Vec2.create({ x: 0, y: 330 }) },
textColor: Vec3.create({ r: 100, g: 100, b: 100 }),
}}
>
...还有 {tabContent[tab].length - 10} 项
</text>
)}
</box>
</box>
</box>
);
}
useTransition 钩子
React 还提供了 useTransition
钩子,它返回一个状态标志和一个函数,避免手动管理 pending 状态:
tsx
import React, { hooks } from "@dao3fun/react";
const { useTransition, useState } = hooks;
function ImprovedTabsExample() {
const [tab, setTab] = useState("home");
// 获取isPending状态和startTransition函数
const [isPending, startTransition] = useTransition();
// 与前面相同的内容...
const handleTabChange = (newTab) => {
// 使用来自钩子的startTransition
startTransition(() => {
setTab(newTab);
});
};
// 其余代码与前例类似...
}
使用场景
- 导航更新:在页面导航时保持 UI 响应
- 搜索结果:在用户输入时保持输入流畅,延迟显示结果
- 复杂数据更新:需要处理大量数据时保持 UI 响应性
- 多步骤表单:在表单提交流程中保持响应性
注意事项
- 紧急与非紧急更新:区分哪些更新需要立即执行,哪些可以延迟
- 过渡不会取消:过渡更新只是降低优先级,不会被取消
- 适用范围:仅适用于 React 状态更新,不适用于数据加载或异步操作
memo
memo
是一个高阶组件,用于优化函数组件的渲染性能。它通过记忆组件渲染结果,在 props 没有变化时跳过重新渲染。
基本用法
tsx
import React, { memo, hooks } from "@dao3fun/react";
const { useState } = hooks;
// 未优化的组件
function Button({ label, onClick }) {
console.log("Button 组件渲染", label);
return (
<box
style={{
backgroundColor: Vec3.create({ r: 66, g: 133, b: 244 }),
size: { offset: Vec2.create({ x: 120, y: 40 }) },
}}
onClick={onClick}
>
<text
style={{
textColor: Vec3.create({ r: 255, g: 255, b: 255 }),
position: { offset: Vec2.create({ x: 10, y: 10 }) },
}}
>
{label}
</text>
</box>
);
}
// 使用 memo 优化的组件
const MemoizedButton = memo(Button);
// 使用示例
function App() {
const [count, setCount] = useState(0);
// 这个处理函数每次渲染都会创建新实例
const handleClick = () => {
setCount(count + 1);
};
return (
<box>
<text>计数: {count}</text>
{/* 未优化的按钮,每次父组件渲染都会重新渲染 */}
<box style={{ position: { offset: Vec2.create({ x: 0, y: 50 }) } }}>
<Button label="普通按钮" onClick={handleClick} />
</box>
{/* 优化的按钮,但因为 onClick 每次都是新函数,仍会重新渲染 */}
<box style={{ position: { offset: Vec2.create({ x: 0, y: 100 }) } }}>
<MemoizedButton label="Memo按钮(仍会重渲染)" onClick={handleClick} />
</box>
</box>
);
}
自定义比较函数
memo
接受一个可选的第二个参数,用于自定义前后 props 的比较逻辑:
tsx
const MemoizedComponent = memo(MyComponent, (prevProps, nextProps) => {
// 返回 true 表示组件不需要重新渲染
// 返回 false 表示组件需要重新渲染
return prevProps.value === nextProps.value;
});
与 useCallback 配合使用
要充分发挥 memo
的效果,通常需要与 useCallback
配合使用,确保传递给子组件的函数引用保持稳定:
tsx
function OptimizedApp() {
const [count, setCount] = useState(0);
// 使用 useCallback 记忆函数引用
const handleClick = useCallback(() => {
setCount((prev) => prev + 1);
}, []); // 空依赖数组,函数引用永远不变
return (
<box>
<text>计数: {count}</text>
{/* 现在这个组件只会在 label 变化时重新渲染 */}
<box style={{ position: { offset: Vec2.create({ x: 0, y: 50 }) } }}>
<MemoizedButton label="优化的按钮" onClick={handleClick} />
</box>
</box>
);
}
何时使用 memo
memo
在以下情况下特别有用:
- 组件渲染开销大:组件包含复杂计算或大量 DOM 元素
- 组件频繁重新渲染:父组件经常更新,但子组件的 props 很少变化
- 组件在列表中重复使用:在
map
循环中渲染的组件,尤其是大型列表
tsx
// 列表项组件优化示例
const ListItem = memo(({ item, onSelect }) => {
console.log("渲染项目:", item.id);
return (
<box
style={{
backgroundColor: Vec3.create({ r: 240, g: 240, b: 240 }),
size: { offset: Vec2.create({ x: 300, y: 40 }) },
}}
onClick={() => onSelect(item.id)}
>
<text>{item.name}</text>
</box>
);
});
function List({ items }) {
const [selectedId, setSelectedId] = useState(null);
// 记忆选择函数
const handleSelect = useCallback((id) => {
setSelectedId(id);
}, []);
return (
<box>
{items.map((item) => (
<ListItem key={item.id} item={item} onSelect={handleSelect} />
))}
<text>已选择: {selectedId}</text>
</box>
);
}
注意事项
- 不要过度使用:对于渲染开销很小的组件,
memo
可能会增加不必要的复杂度 - 检查依赖项:确保传递给记忆化组件的 props 是稳定的(使用
useCallback
、useMemo
处理函数和对象) - 深度比较陷阱:默认使用浅比较,对于复杂对象需要自定义比较函数或使用
useMemo
保持引用稳定
最佳实践
何时使用记忆化优化
- 先测量,后优化:使用开发工具确认性能瓶颈,再有针对性地优化
- 关注热路径:优先优化频繁渲染且计算密集的组件
- 保持 props 稳定性:使用
useCallback
、useMemo
稳定 props 引用
避免的陷阱
内联对象和函数:避免直接在 JSX 中创建对象和函数
tsx// 不推荐 - 每次渲染都创建新对象 <MemoizedComponent style={{ color: "red" }} />; // 推荐 - 使用 useMemo 稳定对象引用 const style = useMemo(() => ({ color: "red" }), []); <MemoizedComponent style={style} />;
不必要的记忆化:不要对简单组件应用
memo
tsx// 通常不需要记忆化的简单组件 const Label = ({ text }) => <text>{text}</text>;
忽略依赖项:确保
useCallback
和useMemo
的依赖数组包含所有必要依赖tsx// 错误 - 缺少依赖项 const handleClick = useCallback(() => { console.log(value); // value 是外部变量 }, []); // 应该包含 [value]
性能优化策略
性能优化应当遵循以下策略:
- 首先确保组件设计合理,避免不必要的嵌套和渲染
- 合理拆分组件,将变化频繁的部分与稳定的部分分离
- 谨慎使用
memo
、useCallback
和useMemo
- 对于极端性能要求的场景,考虑使用虚拟滚动等技术