Hooks
组件状态、Context、缓存和副作用。
Hooks 从 onescript/views 导入,为函数组件提供状态、Context、缓存和副作用能力。
Hooks 为函数组件提供本地状态、Context 和生命周期副作用。请在组件顶层调用 Hooks,并在每次渲染时保持调用顺序和数量稳定。
useState
useState 为每个已挂载的组件实例保存本地状态。
function useState<T>(
initialState: T | (() => T)
): readonly [T, (action: T | ((previous: T) => T)) => void]传入函数可以执行一次惰性初始化。setter 支持直接传入下一个值,或传入接收旧值的更新函数。OneScript 会批处理连续更新,并在新值通过 Object.is 比较保持相等时跳过渲染。
import { VStack, Text, TextField, useState } from "onescript/views"
function Profile() {
const [name, setName] = useState("")
return (
<VStack spacing={8}>
<TextField
value={name}
onChange={setName}
prompt="姓名"
/>
<Text>你好,{name}</Text>
</VStack>
)
}useReducer
useReducer 使用 reducer 集中管理组件状态。reducer 接收当前 state 和 action,并返回下一份 state;dispatch 接收一个 action,并安排组件使用新状态重新渲染。
type Reducer<State, Action> = (state: State, action: Action) => State
type ReducerDispatch<Action> = (action: Action) => void
function useReducer<State, Action>(
reducer: Reducer<State, Action>,
initialState: State
): readonly [State, ReducerDispatch<Action>]
function useReducer<State, Action, InitialArg>(
reducer: Reducer<State, Action>,
initialArg: InitialArg,
init: (initialArg: InitialArg) => State
): readonly [State, ReducerDispatch<Action>]import { VStack, Text, TextField, useReducer } from "onescript/views"
type State = { name: string; changes: number }
type Action = { type: "changedName"; name: string } | { type: "reset" }
function reducer(state: State, action: Action): State {
switch (action.type) {
case "changedName":
return { name: action.name, changes: state.changes + 1 }
case "reset":
return { name: "", changes: 0 }
}
}
function ProfileEditor() {
const [state, dispatch] = useReducer(reducer, { name: "", changes: 0 })
return (
<VStack spacing={8}>
<TextField
value={state.name}
onChange={name => dispatch({ type: "changedName", name })}
/>
<Text>修改次数:{state.changes}</Text>
</VStack>
)
}state 和 action 可以使用任意类型。reducer 应保持纯函数,并返回新的对象或数组来表达变化。dispatch 的引用在组件生命周期内保持稳定,可以安全地作为 useEffect 依赖。
OneScript 会批处理连续 dispatch,并依次将每个 action 应用于最新 reducer state。reducer 返回与当前 state 通过 Object.is 比较相等的值时跳过渲染。调用 dispatch 后,当前事件处理函数中的 state 仍是本次渲染的快照。
第三个 init 参数用于昂贵的惰性初始化,并且只在组件首次挂载时执行一次:
const [state, dispatch] = useReducer(reducer, username, createInitialState)省略 init 时,initialState 会直接成为初始状态;函数类型的 initialState 也会作为普通值保存。组件后续渲染传入的新 initial state 或 initial arg 不会重置已有状态。reducer、initializer 或 dispatch 使用错误会通过现有脚本错误通道报告。
createContext 与 useContext
createContext 创建可以跨组件层级传递数据的 Context。useContext 返回调用组件上方最近 Provider 的值;组件上方没有对应 Provider 时返回创建 Context 时传入的静态默认值。
interface ContextProviderProps<T> {
readonly value: T
readonly children?: ViewChild
}
interface Context<T> extends ViewComponent<ContextProviderProps<T>> {
readonly Provider: Context<T>
readonly Consumer: ViewComponent<{
readonly children: (value: T) => ViewChild
}>
}
function createContext<T>(defaultValue: T): Context<T>
function useContext<T>(context: Context<T>): T将 Context 作为组件,并通过 value 为子组件提供共享数据:
import { Text, TextField, createContext, useContext, useState } from "onescript/views"
const ThemeContext = createContext("light")
function Greeting() {
const theme = useContext(ThemeContext)
return <Text>当前主题:{theme}</Text>
}
function Screen() {
const [theme, setTheme] = useState("dark")
return (
<ThemeContext value={theme}>
<TextField
value={theme}
onChange={setTheme}
/>
<Greeting />
</ThemeContext>
)
}<ThemeContext.Provider value={theme}> 提供相同的兼容写法。ThemeContext.Consumer 接收以当前值为参数的 children 函数,适合迁移 legacy 组件。
Provider 可以嵌套,最近的同类型 Provider 会覆盖外层值。显式传入 value={undefined} 会向消费者提供 undefined;省略 value 时同样提供 undefined 并打印警告。Provider 所在组件调用 useContext 时读取该 Provider 上方的值。
Provider 使用 Object.is 比较前后值。值发生变化时,所有订阅该 Provider 的消费者都会获得最新值,稳定 props 的中间组件不会阻断传播。对象或函数 Context 值可以通过 useMemo 和 useCallback 保持引用稳定:
const login = useCallback(response => saveSession(response), [])
const account = useMemo(() => ({ user, login }), [user, login])
return <AccountContext value={account}><Profile /></AccountContext>useCallback
useCallback 用于在多次渲染之间缓存函数引用。首次渲染时,OneScript 返回传入的函数;所有依赖通过 Object.is 比较保持相等时继续返回同一函数;任一依赖变化时采用当前渲染传入的新函数。
type DependencyList = readonly unknown[]
function useCallback<T extends (...args: any[]) => any>(
callback: T,
dependencies: DependencyList
): Timport { TextField, useCallback, useEffect, useState } from "onescript/views"
function Search({ userID }: { userID: string }) {
const [query, setQuery] = useState("")
const loadResults = useCallback(() => {
void search(userID, query)
}, [userID, query])
useEffect(() => {
loadResults()
}, [loadResults])
return (
<TextField
value={query}
onChange={setQuery}
prompt="搜索"
/>
)
}回调没有读取会变化的 props 或 state 时可以传入 []。依赖数组需要包含回调读取的所有响应式值。稳定的 callback 可以保持 Effect 依赖不变,也能让 OneScript 复用其他 props 同样浅比较相等的子组件。
TypeScript API 要求传入依赖数组。JavaScript 代码省略依赖时,每次渲染都会获得当前传入的函数。组件生命周期内需要保持依赖模式和数组长度稳定。
useMemo
useMemo 用于在多次渲染之间缓存计算结果。首次渲染时执行计算函数;所有依赖通过 Object.is 比较保持相等时返回缓存值;任一依赖变化时使用当前闭包重新计算。
function useMemo<T>(
calculateValue: () => T,
dependencies: DependencyList
): Timport { Text, useMemo, useState } from "onescript/views"
function Factorial() {
const [count, setCount] = useState(5)
const factorial = useMemo(() => {
let result = 1
for (let i = 1; i <= count; i++) result *= i
return result
}, [count])
return <Text>{count}! = {factorial}</Text>
}计算函数应保持纯函数、接收零个参数,并通过闭包读取所需值。依赖数组需要包含计算函数读取的所有响应式值。传入 [] 会在当前组件实例的生命周期内持续复用首次结果。
useMemo 可以稳定对象、数组等引用,让浅比较相等的子组件复用输出,也可以保持 useEffect 的对象依赖稳定。useCallback(callback, dependencies) 对应缓存函数引用,useMemo(() => value, dependencies) 对应缓存计算结果。
TypeScript API 要求传入依赖数组。JavaScript 代码省略依赖时,每次渲染都会重新计算。组件生命周期内需要保持依赖模式和数组长度稳定。useMemo 是渲染性能优化,计算结果可以是任意 JavaScript 值。
useEffect
useEffect 用于将已挂载的组件与定时器、订阅、网络请求等外部系统同步。原生视图提交成功后,OneScript 执行 setup。setup 可以返回 cleanup 函数,在 effect 重新执行或组件卸载前释放资源。
type EffectCleanup = () => void
type EffectCallback = () => void | EffectCleanup
type DependencyList = readonly unknown[]
function useEffect(
setup: EffectCallback,
dependencies?: DependencyList
): voidimport { Text, useEffect, useState } from "onescript/views"
function Clock() {
const [seconds, setSeconds] = useState(0)
useEffect(() => {
const timer = setInterval(() => {
setSeconds(previous => previous + 1)
}, 1000)
return () => clearInterval(timer)
}, [])
return <Text>已运行 {seconds} 秒</Text>
}| 依赖参数 | 行为 |
|---|---|
| 省略 | 每次组件成功提交后执行。 |
[] | 原生视图首次挂载后执行一次。 |
[value1, value2] | 首次挂载后执行,并在任一依赖通过 Object.is 比较发生变化时重新执行。 |
依赖变化时,OneScript 会先执行上一次 cleanup,再执行新的 setup。会话终止时也会在释放 JavaScript 运行时之前执行所有生效中的 cleanup。
Effect setup 采用同步函数,并返回 cleanup 函数或 undefined。异步工作应在 effect 内部启动:
useEffect(() => {
let active = true
void loadProfile().then(profile => {
if (active) setProfile(profile)
})
return () => {
active = false
}
}, [userID])Widget 中的行为
Widget 采用静态渲染。useState 和 useReducer 的初始状态会参与生成视图,useContext 会读取默认值或最近 Provider 的值,useCallback 会返回传入的函数,useMemo 会执行计算函数并使用结果。setter 和 dispatch 不会产生 Widget 更新,Widget 渲染会跳过 useEffect setup 和 cleanup。请使用 Widget Timeline 安排内容更新。
How is this guide?
最后更新于