快速开始
欢迎使用 Scripting!这是一款可让你使用 TypeScript 编写 React 类似的 TSX 语法来创建 UI 组件和自定义小组件、灵动岛和使用发通知提醒等能力的应用。通过 Scripting,你可以使用包装过的 SwiftUI 视图来获得在 iOS 上流畅且原生的使用体验,并通过熟悉的编码结构来创建和呈现各种 iOS 工具型 UI 页面。本指南将带你完成项目设置、组件创建以及结合 Hooks 构建动态界面的流程。
目录
- 快速开始
- 创建脚本项目
- 导入组件
- 创建自定义组件
- 呈现 UI 视图
- 使用 Hooks
- 构建复杂的 UI
1. 快速开始
在 Scripting 中,你可以通过定义函数式组件的方式来创建简单的 UI 元素。你需要的所有组件和 API 都可以从 scripting
包里导入。
2. 创建脚本项目
在开始编写代码之前,你需要创建一个脚本项目。项目创建完成后,你可以在 index.tsx
文件中编写代码。这个文件是定义 UI 组件和逻辑的主要入口。
index.tsx
的示例:
1import { VStack, Text } from "scripting"
2
3// 定义一个自定义视图组件
4function View() {
5 return (
6 <VStack>
7 <Text>Hello, Scripting!</Text>
8 </VStack>
9 )
10}
3. 导入视图
SwiftUI 中的所有视图以及部分 API 都进行了包装,并通过 scripting
包提供给你使用。以下是部分可用视图的列表:
- 布局视图:
VStack
, HStack
, ZStack
, Grid
- 控件:
Button
, Picker
, Toggle
, Slider
, ColorPicker
- 集合:
List
, Section
- 日期和时间:
DatePicker
- 文本和标签:
Text
, Label
, TextField
你可以像这样在项目中导入它们:
1import { VStack, Text, Button, Picker } from "scripting"
4. 创建自定义组件
在 Scripting 中,函数式组件的工作原理与 React 基本相同,可以使用类似 JSX 的语法来构建可复用组件。
示例:
1import { VStack, HStack, Text, Button } from "scripting"
2
3function Greeting({
4 name
5}: {
6 name: string
7}) {
8 return (
9 <HStack>
10 <Text>Hello, {name}!</Text>
11 </HStack>
12 )
13}
14
15function MainView() {
16 return (
17 <VStack>
18 <Greeting name="Scripting User" />
19 <Button
20 title="Click Me"
21 action={() => console.log("Button Clicked!")}
22 />
23 </VStack>
24 )
25}
5. 呈现 UI 视图
若要呈现 UI 视图,可以使用 Navigation.present
方法。它能够以模态视图的形式显示自定义组件,并处理该视图的关闭。Navigation.present
方法会返回一个在视图被关闭后才会完成的 Promise。为了避免内存泄漏,一定要在视图关闭后调用 Script.exit()
。
示例:
1import { VStack, Text, Navigation, Script } from "scripting"
2
3function View() {
4 return (
5 <VStack>
6 <Text>Hello, Scripting!</Text>
7 </VStack>
8 )
9}
10
11// 显示该视图
12Navigation.present({
13 element: <View />
14}).then(() => {
15 // 视图关闭后清理资源,避免内存泄漏
16 Script.exit()
17})
在上述示例中,Navigation.present({ element: <View /> })
会呈现 View
组件;当用户关闭此视图后,Script.exit()
确保释放相关资源。
6. 使用 Hooks
Scripting 支持一系列与 React 类似的 Hooks,用于管理组件中的状态、副作用、Memo 化以及上下文。以下是每种 Hook 的使用指南及示例:
useState
useState
Hook 能够让你在函数式组件中添加本地状态。
1import { useState, VStack, Text, Button } from "scripting"
2
3function Counter() {
4 const [count, setCount] = useState(0)
5
6 return (
7 <VStack>
8 <Text>Count: {count}</Text>
9 <Button
10 title="Increment"
11 action={() => setCount(count + 1)}
12 />
13 </VStack>
14 )
15}
在这个示例中,每次点击按钮都会更新 count
变量,并触发组件的自动重新渲染。
useEffect
useEffect
Hook 可以让你在组件中执行副作用操作,比如获取数据或者设置订阅。
1import { useState, useEffect, VStack, Text } from "scripting"
2
3function TimeDisplay() {
4 const [time, setTime] = useState(
5 new Date().toLocaleTimeString()
6 )
7
8 useEffect(() => {
9 let timerId: number
10
11 const startTimer = () => {
12 timerId = setTimeout(() => {
13 setTime(new Date().toLocaleTimeString())
14 }, 1000)
15 }
16
17 startTimer()
18
19 return () => clearTimeout(timerId) // 组件卸载时清理定时器
20 }, [])
21
22 return <Text>Current Time: {time}</Text>
23}
在此示例中,useEffect
Hook 会设置一个间隔操作,每秒更新一次 time
变量,并在组件卸载时清除该间隔以避免潜在的问题。
useReducer
当你需要在组件中管理更复杂的状态逻辑时,useReducer
Hook 非常有用。
1import { useReducer, VStack, Text, Button } from "scripting"
2
3type Action = {
4 type: "increment"
5} | {
6 type: "decrement"
7}
8const reducer = (state: number, action: Action) => {
9 switch (action.type) {
10 case "increment":
11 return state + 1
12 case "decrement":
13 return state - 1
14 default:
15 return state
16 }
17}
18
19function Counter() {
20 const [count, dispatch] = useReducer(reducer, 0)
21
22 return (
23 <VStack>
24 <Text>Count: {count}</Text>
25 <Button
26 title="Increment"
27 action={() => dispatch({ type: "increment" })}
28 />
29 <Button
30 title="Decrement"
31 action={() => dispatch({ type: "decrement" })}
32 />
33 </VStack>
34 )
35}
useReducer
Hook 可以通过一个 reducer 函数来帮助你更好地处理复杂的状态变更。
useCallback
useCallback
Hook 可以让你对函数进行 Memo 化,以避免在每次渲染时都重新创建函数,从而提升性能。
1import { useState, useCallback, VStack, Text, Button } from "scripting"
2
3function Counter() {
4 const [count, setCount] = useState(0)
5
6 const increment = useCallback(() => {
7 setCount((prev) => prev + 1)
8 }, [])
9
10 return (
11 <VStack>
12 <Text>Count: {count}</Text>
13 <Button
14 title="Increment"
15 action={increment}
16 />
17 </VStack>
18 )
19}
使用 useCallback
,只有在依赖项改变时才会重新创建 increment
函数,从而在大型或频繁更新的组件中提升性能。
useMemo
useMemo
Hook 允许你对某些值进行 Memo 化,以缓存代价高的计算结果,从而提高性能。
1import { useState, useMemo, VStack, Text, Button } from "scripting"
2
3function FactorialCounter() {
4 const [count, setCount] = useState(1)
5
6 const factorial = useMemo(() => {
7 let result = 1
8 for (let i = 1; i <= count; i++) result *= i
9 return result
10 }, [count])
11
12 return (
13 <VStack>
14 <Text>Factorial of {count} is {factorial}</Text>
15 <Button
16 title="Increase"
17 action={() => setCount(count + 1)}
18 />
19 </VStack>
20 )
21}
useMemo
Hook 仅在 count
改变时才重新计算阶乘,从而避免不必要的性能消耗。
useContext
useContext
Hook 允许你在应用的各组件之间共享状态,而无需进行层层的 props 传递(即“向下传递”)。
1import { createContext, useContext, VStack, Text, Button } from "scripting"
2
3const CountContext = createContext<number>()
4
5function Display() {
6 const count = useContext(CountContext)
7 return <Text>Shared Count: {count}</Text>
8}
9
10function App() {
11 return (
12 <CountContext.Provider value={42}>
13 <VStack>
14 <Display />
15 </VStack>
16 </CountContext.Provider>
17 )
18}
在此示例中,useContext
可以访问 CountContext
,从而在应用中共享计数值。
7. 构建复杂的 UI
通过结合已提供的视图、Hooks 和自定义组件,你可以构建出功能完善、结构复杂的 UI。
示例:
1import { useState, VStack, Text, TextField, List, Section, NavigationStack, Script } from "scripting"
2
3function ToDoApp() {
4 const [tasks, setTasks] = useState(["Task 1", "Task 2", "Task 3"])
5 const [content, setContent] = useState("")
6
7 return (
8 <NavigationStack>
9 <List
10 navigationTitle="My Tasks"
11 >
12 <Section>
13 {tasks.map((task, index) => (
14 <Text key={index}>{task}</Text>
15 ))}
16 </Section>
17
18 <TextField
19 title="New Task"
20 value={content}
21 onChanged={setContent}
22 onSubmit={() => {
23 if (content.length === 0) {
24 return
25 }
26 setTasks([...tasks, content])
27 setContent("")
28 }}
29 />
30 </List>
31 </NavigationStack>
32 )
33}
34
35async function run() {
36 await Navigation.present({
37 element: <ToDoApp />
38 })
39
40 Script.exit()
41}
如需了解更多详细信息,请查阅完整的 API 文档,该文档包含关于 scripting
包的更多示例和使用场景。