From a1f4ecbefd025e9556e2410e843143a756741acc Mon Sep 17 00:00:00 2001 From: hoikan Date: Mon, 5 May 2025 01:03:01 +0800 Subject: [PATCH] docs: add next version docs --- docs/next/components/Dynamic.md | 63 +++ docs/next/components/ErrorBoundary.md | 54 ++ docs/next/components/Fragment.md | 68 +++ docs/next/components/Portal.md | 64 +++ docs/next/components/Suspense.md | 54 ++ docs/next/components/composition.md | 120 ++++ docs/next/components/context.md | 90 +++ docs/next/components/props.md | 125 +++++ docs/next/core-concepts/introduction.md | 152 +++++ docs/next/getting-started.md | 143 +++++ docs/next/index.md | 115 ++++ docs/next/introduction.md | 41 ++ docs/next/lifecycle/cleanup.md | 59 ++ docs/next/lifecycle/mounting.md | 49 ++ docs/next/lifecycle/unmounting.md | 43 ++ docs/next/reactivity/computed.md | 79 +++ docs/next/reactivity/state.md | 293 ++++++++++ docs/next/reactivity/untrack.md | 124 ++++ docs/next/reactivity/watch.md | 114 ++++ docs/next/template-system/conditional.md | 208 +++++++ docs/next/template-system/event-handling.md | 304 ++++++++++ docs/next/template-system/list-rendering.md | 257 +++++++++ docusaurus.config.js | 6 + package-lock.json | 592 +++++++++++++++++--- sidebars.js | 45 +- src/css/custom.css | 22 +- 26 files changed, 3197 insertions(+), 87 deletions(-) create mode 100644 docs/next/components/Dynamic.md create mode 100644 docs/next/components/ErrorBoundary.md create mode 100644 docs/next/components/Fragment.md create mode 100644 docs/next/components/Portal.md create mode 100644 docs/next/components/Suspense.md create mode 100644 docs/next/components/composition.md create mode 100644 docs/next/components/context.md create mode 100644 docs/next/components/props.md create mode 100644 docs/next/core-concepts/introduction.md create mode 100644 docs/next/getting-started.md create mode 100644 docs/next/index.md create mode 100644 docs/next/introduction.md create mode 100644 docs/next/lifecycle/cleanup.md create mode 100644 docs/next/lifecycle/mounting.md create mode 100644 docs/next/lifecycle/unmounting.md create mode 100644 docs/next/reactivity/computed.md create mode 100644 docs/next/reactivity/state.md create mode 100644 docs/next/reactivity/untrack.md create mode 100644 docs/next/reactivity/watch.md create mode 100644 docs/next/template-system/conditional.md create mode 100644 docs/next/template-system/event-handling.md create mode 100644 docs/next/template-system/list-rendering.md diff --git a/docs/next/components/Dynamic.md b/docs/next/components/Dynamic.md new file mode 100644 index 0000000..e23d28a --- /dev/null +++ b/docs/next/components/Dynamic.md @@ -0,0 +1,63 @@ +--- +id: Dynamic +title: Dynamic +sidebar_label: Dynamic +--- + +# Dynamic + +`Dynamic` 组件用于动态渲染不同的组件类型,支持 props 传递、context、生命周期等,适合需要根据状态切换渲染内容的场景。 + +## 典型用法 + +```tsx +function Hello() { return
Hello
; } +function World() { return
World
; } + +``` +- 支持 props 透传、context 获取、生命周期钩子。 +- 支持组件切换时自动卸载/挂载。 + +## props/context/生命周期 + +- 支持 props 传递: +```tsx + +``` +- 支持 context: +```tsx +const Ctx = createContext(0); + + + +``` +- 支持 didUnmount/willUnmount 等生命周期钩子。 + +## 多元素/事件/空组件 + +- 支持组件返回多个元素(Fragment): +```tsx +function Multi() { return <>
First
Second
; } + +``` +- 支持事件处理: +```tsx +function Btn({ onClick }) { return ; } + +``` +- component 可为 null/undefined,渲染为空。 + +## 注意事项 + +- component 可为 null/undefined,渲染为空。 +- 支持多种 props 结构、rest props。 +- 支持多元素返回、事件处理。 + +## 最佳实践 + +- 用于需要动态切换组件的场景,如 tab、动态表单等。 +- props 建议使用解构,便于类型推导和响应式。 + +## 相关链接 + +- [React Dynamic Rendering](https://react.dev/reference/react/cloneElement) \ No newline at end of file diff --git a/docs/next/components/ErrorBoundary.md b/docs/next/components/ErrorBoundary.md new file mode 100644 index 0000000..041a9b2 --- /dev/null +++ b/docs/next/components/ErrorBoundary.md @@ -0,0 +1,54 @@ +--- +id: ErrorBoundary +title: ErrorBoundary +sidebar_label: ErrorBoundary +--- + +# ErrorBoundary + +`ErrorBoundary` 组件用于捕获其子组件渲染过程中的异常,并渲染备用 UI(fallback)。适用于防止整个应用因单个组件错误而崩溃。 + +## 典型用法 + +```tsx +function BuggyComponent() { + throw new Error('出错了'); + return
不会渲染
; +} + +const Fallback = error =>
{error.message}
; + + + + +``` + +- 当子组件抛出异常时,`fallback` 会接收到 error 对象并渲染对应内容。 +- 正常情况下渲染 children。 + +## 嵌套与错误冒泡 + +支持嵌套 ErrorBoundary,错误会冒泡到最近的 ErrorBoundary: + +```tsx +
外部错误: {outerErr.message}
}> +
内部错误: {innerErr.message}
}> + +
+
+``` + +## 注意事项 + +- `fallback` 必须是函数,参数为 error。 +- 只捕获渲染过程中的错误,不捕获事件处理等异步错误。 +- 多层嵌套时,内层优先捕获。 + +## 最佳实践 + +- 建议在应用入口或关键区域包裹 ErrorBoundary。 +- fallback UI 应简洁明了,便于用户理解。 + +## 相关链接 + +- [React Error Boundaries](https://reactjs.org/docs/error-boundaries.html) \ No newline at end of file diff --git a/docs/next/components/Fragment.md b/docs/next/components/Fragment.md new file mode 100644 index 0000000..4960b10 --- /dev/null +++ b/docs/next/components/Fragment.md @@ -0,0 +1,68 @@ +--- +id: Fragment +title: Fragment +sidebar_label: Fragment +--- + +# Fragment + +`Fragment` 用于包裹多个元素,避免额外的 DOM 节点,常用于返回多个兄弟节点的场景。 + +## 典型用法 + +```tsx +<> +
First
+
Second
+ +``` +- 渲染结果不会多出额外的 DOM 层级。 + +## 嵌套/条件/三元/事件 + +- 支持嵌套: +```tsx +<> +
Start
+ <> +

Nested

+ Child + +
End
+ +``` +- 支持条件渲染: +```tsx +<> +
Always
+ {showExtra && <>
Extra 1
Extra 2
} + +``` +- 支持三元表达式: +```tsx +<> + {cond ? <>
True 1
True 2
: <>
False 1
False 2
} + +``` +- 支持事件处理: +```tsx +<> + +
{clicked ? 'Clicked' : 'Not clicked'}
+ +``` + +## 注意事项 + +- Fragment 不会影响 CSS 或事件。 +- 可与文本、JSX 表达式混用。 +- 支持多层嵌套。 + +## 最佳实践 + +- 用于组件需要返回多个兄弟节点时,避免多余的 div。 +- 可与条件渲染、列表渲染等结合使用。 + +## 相关链接 + +- [React Fragments](https://react.dev/reference/react/Fragment) \ No newline at end of file diff --git a/docs/next/components/Portal.md b/docs/next/components/Portal.md new file mode 100644 index 0000000..1bfb072 --- /dev/null +++ b/docs/next/components/Portal.md @@ -0,0 +1,64 @@ +--- +id: Portal +title: Portal +sidebar_label: Portal +--- + +# Portal + +`Portal` 组件用于将子节点渲染到指定的 DOM 容器外部,常用于模态框、弹窗等场景。 + +## 典型用法 + +```tsx +const portalRoot = document.getElementById('portal-root'); + +
Portal Content
+
+``` +- target 必须为有效的 DOM 元素。 +- 组件卸载时自动移除内容。 + +## 多 Portal 与嵌套 Portal + +- 支持多个 Portal 指向同一目标: + +```tsx +First Content +Second Content +``` +- 支持嵌套 Portal: + +```tsx + +
Outer Content
+ +
Inner Content
+
+
+``` + +## 事件处理 + +- Portal 内的事件(如 onClick)可正常冒泡和处理。 + +```tsx + + + +``` + +## 注意事项 + +- target 必须为已挂载的 DOM 元素。 +- 组件卸载时内容会自动清理。 +- 支持内容和事件的动态更新。 + +## 最佳实践 + +- 用于模态框、弹窗、全局提示等需要脱离父 DOM 层级的场景。 +- 建议统一管理 portal 容器节点。 + +## 相关链接 + +- [React Portal](https://react.dev/reference/react-dom/createPortal) \ No newline at end of file diff --git a/docs/next/components/Suspense.md b/docs/next/components/Suspense.md new file mode 100644 index 0000000..e36f2b5 --- /dev/null +++ b/docs/next/components/Suspense.md @@ -0,0 +1,54 @@ +--- +id: Suspense +title: Suspense & lazy +sidebar_label: Suspense & lazy +--- + +# Suspense & lazy + +`Suspense` 组件用于包裹异步组件(如 `lazy` 加载),在异步加载期间渲染 fallback。`lazy` 用于声明异步加载的组件。 + +## 典型用法 + +```tsx +const LazyComponent = lazy(() => import('./Comp')); + +loading...}> + + +``` + +- fallback 在异步加载期间显示,加载完成后自动切换为真实内容。 +- 支持多个 lazy 组件、嵌套 Suspense。 + +## 嵌套与多组件 + +```tsx +outer loading...}> + + inner loading...}> + + + +``` +- 外层和内层 Suspense 可分别控制不同异步内容的 loading 状态。 + +## 错误处理 + +- 异步加载失败时可结合 ErrorBoundary 捕获错误。 +- lazy 组件必须返回 Promise,resolve 时需有 default 导出。 + +## 注意事项 + +- Suspense 只对 lazy 组件生效。 +- fallback 必须为有效的 React 元素。 +- 支持多层嵌套,内层优先显示 fallback。 + +## 最佳实践 + +- 推荐为每个异步区域单独设置 Suspense,提升用户体验。 +- fallback UI 应简洁明了。 + +## 相关链接 + +- [React Suspense](https://react.dev/reference/react/Suspense) \ No newline at end of file diff --git a/docs/next/components/composition.md b/docs/next/components/composition.md new file mode 100644 index 0000000..d26b76e --- /dev/null +++ b/docs/next/components/composition.md @@ -0,0 +1,120 @@ +--- +id: composition +title: 组件组合 +sidebar_label: 组件组合 +--- + +# 组件组合 + +组件组合是构建复杂 UI 的基础。openInula 2支持多种组合模式,包括容器组件、复合组件、插槽(children)、高阶组件等。 + +## 容器组件与展示组件 + +将逻辑与展示分离,提升复用性。 + +```tsx +// 展示组件 +function UserCard({ user }) { + return ( +
+

{user.name}

+

邮箱:{user.email}

+
+ ); +} + +// 容器组件 +function UserList({ users }) { + return ( +
+ + {(user) => } + +
+ ); +} +``` + +## 插槽(children) + +通过 `children` 实现灵活的内容插入。 + +```tsx +function Panel({ title, children }) { + return ( +
+

{title}

+
{children}
+
+ ); +} + +function App() { + return ( + +

这是插槽内容

+ +
+ ); +} +``` + +## 复合组件模式 + +将多个子组件组合为一个整体。 + +```tsx +function Tabs({ children }) { + // 省略 tab 切换逻辑 + return
{children}
; +} + +function Tab({ label, children }) { + return ( +
+

{label}

+
{children}
+
+ ); +} + +function App() { + return ( + + 内容A + 内容B + + ); +} +``` + +## 高阶组件(HOC) + +高阶组件用于复用逻辑或增强组件功能。 + +```tsx +function withLogger(Component) { + return function Wrapper(props) { + console.log('props:', props); + return ; + }; +} + +const LoggedPanel = withLogger(Panel); +``` + +## 最佳实践 + +- 组件应职责单一,便于组合和复用 +- 善用 children 和 props 传递内容和行为 +- 复合组件可提升灵活性 + +## 注意事项 + +- 避免过度嵌套,保持结构清晰 +- 插槽内容建议通过 children 传递 + +## 相关链接 + +- [Props 传递](./props) +- [上下文](./context) \ No newline at end of file diff --git a/docs/next/components/context.md b/docs/next/components/context.md new file mode 100644 index 0000000..e0a665f --- /dev/null +++ b/docs/next/components/context.md @@ -0,0 +1,90 @@ +--- +id: context +title: 上下文 +sidebar_label: 上下文 +--- + +# 上下文(Context) + +openInula 2(zouyu)支持类似 React 的 Context 机制,用于在组件树中跨层级传递数据,避免 props 层层传递。Context 机制包括 Provider(提供者)和 Consumer(消费者),并且支持响应式更新。 + +## 基本用法 + +### 1. 创建 Context + +```tsx +import { createContext } from 'openinula'; + +const UserContext = createContext({ level: 0, path: '' }); +``` + +### 2. Provider(提供者) + +通过将 context 作为组件标签包裹子组件,并传递属性来提供数据: + +```tsx +function App() { + let level = 1; + let path = '/home'; + return ( + + + + ); +} +``` + +- context 属性可以是任意响应式变量,变化时会自动通知所有消费组件。 + +### 3. Consumer(消费者) + +在子组件中通过 `useContext` 获取 context 数据: + +```tsx +import { useContext } from 'openinula'; + +function Child() { + const { level, path } = useContext(UserContext); + return
{level} - {path}
; +} +``` + +- 解构 useContext 返回的对象即可获得 context 的所有属性。 +- context 属性变化时,消费组件会自动响应更新。 + +## 响应式原理 + +- context 属性是响应式的,父组件更新属性,所有 useContext 的子组件会自动重新渲染。 +- 编译器会自动为每个属性生成依赖追踪和更新函数。 + +## 只消费部分属性 + +可以只消费 context 的部分属性,只有被消费的属性变化时才会触发更新。 + +```tsx +function OnlyLevel() { + const { level } = useContext(UserContext); + return {level}; +} +``` + +## 多层嵌套与多 context + +可以嵌套多个 context,useContext 会自动查找最近的 Provider。 + +## 最佳实践 + +- 用于全局配置、用户信息、主题等场景 +- 避免频繁变化的小数据放入 context +- 推荐解构useContext返回值,帮助响应式系统能够只在对应 key 更新时,仅更新对应 value 的消费组件,提升性能 + +## 注意事项 + +- context 属性必须通过 Provider 传递,且应为响应式变量 +- useContext 只能在函数组件中调用 +- context 变化会导致所有消费组件重新渲染 + +## 相关链接 + +- [组件组合](./composition) +- [官方测试用例参考](https://github.com/openinula/openinula/blob/main/next-packages/compiler/babel-inula-next-core/test/e2e/context.test.tsx) \ No newline at end of file diff --git a/docs/next/components/props.md b/docs/next/components/props.md new file mode 100644 index 0000000..628ac56 --- /dev/null +++ b/docs/next/components/props.md @@ -0,0 +1,125 @@ +--- +id: props +title: Props 传递 +sidebar_label: Props 传递 +--- + +# Props 传递 + +在 openInula 2(zouyu)中,props 用于父组件向子组件传递数据和回调函数,语法与 React 类似,但更贴近原生 JS。 + +## 基本用法 + +```tsx +function Welcome({ name }) { + return

你好,{name}!

; +} + +function App() { + return ; +} +``` + +## 默认值与解构 + +可以为 props 设置默认值,并使用解构赋值: + +```tsx +function UserProfile({ name = '匿名', age = 0 }) { + return ( +
+

姓名:{name}

+

年龄:{age}

+
+ ); +} +``` + +## 传递回调函数 + +父组件可以通过 props 向子组件传递事件处理函数,实现子组件向父组件通信: + +```tsx +function Counter({ onIncrement }) { + return ; +} + +function App() { + let count = 0; + function handleIncrement() { + count++; + } + return ( +
+

计数:{count}

+ +
+ ); +} +``` + +## 传递对象和数组 + +可以直接传递对象、数组等复杂数据类型: + +```tsx +function TodoList({ todos }) { + return ( +
    + + {(todo) =>
  • {todo.text}
  • } +
    +
+ ); +} + +function App() { + let todos = [ + { text: '学习 OpenInula' }, + { text: '写文档' } + ]; + return ; +} +``` + +## children 插槽 + +通过 `children` 实现插槽功能: + +```tsx +function Panel({ title, children }) { + return ( +
+

{title}

+
{children}
+
+ ); +} + +function App() { + return ( + +

这是插槽内容

+
+ ); +} +``` + +## 最佳实践 + +- 使用解构赋值提升代码可读性,并帮助响应式系统能够只在对应 props key 更新时,更新组件 +- 为 props 设置合理的默认值 +- 只传递必要的数据,避免 props 过多 +- 通过回调函数实现父子通信 + +## 注意事项 + +- props 是只读的,不要在子组件内部修改 props +- children 也是 props 的一部分 +- 复杂数据建议使用不可变数据结构 + +## 下一步 + +- 学习[组件组合](./composition)的高级用法 +- 了解[上下文](./context)的全局数据传递 +- 探索[生命周期](../lifecycle/mounting)的管理 \ No newline at end of file diff --git a/docs/next/core-concepts/introduction.md b/docs/next/core-concepts/introduction.md new file mode 100644 index 0000000..08121c2 --- /dev/null +++ b/docs/next/core-concepts/introduction.md @@ -0,0 +1,152 @@ +--- +id: introduction +title: 基础概念 +sidebar_label: 基础概念 +--- +# OpenInula 基础概念 + +## 组件定义 + +在 openInula 2版本中,组件的定义非常简单直观。你只需要编写一个返回 JSX 的函数即可: + +```tsx +function Welcome() { + return

欢迎使用 OpenInula

; +} +``` + +## 状态管理 + +OpenInula 采用直观的状态管理方式,无需特殊的 API: + +```tsx +function Counter() { + // 直接声明状态变量 + let count = 0; + + // 直接修改状态 + function increment() { + count++; + } + + return ( +
+

当前计数:{count}

+ +
+ ); +} +``` + +## 计算值 + +当一个值依赖于其他状态时,OpenInula 会自动将其转换为计算值: + +```tsx +function PriceCalculator() { + let quantity = 1; + let unitPrice = 100; + + // price 会自动成为计算值 + const price = quantity * unitPrice; + + return ( +
+

数量:{quantity}

+

单价:{unitPrice}

+

总价:{price}

+ +
+ ); +} +``` + +## 条件渲染 + +OpenInula 提供了内置的条件渲染标签: + +```tsx +function Greeting({ isLoggedIn }) { + return ( +
+ +

欢迎回来!

+
+ +

请先登录

+
+
+ ); +} +``` + +## 列表渲染 + +使用内置的 `for` 标签进行列表渲染: + +```tsx +function TodoList() { + const todos = ['学习 OpenInula', '写文档', '创建示例']; + + return ( +
    + + {(todo) =>
  • {todo}
  • } +
    +
+ ); +} +``` + +## 事件处理 + +事件处理采用 `onXxx` 的形式: + +```tsx +function Button() { + let clickCount = 0; + + return ( + + ); +} +``` + +## 组件组合 + +组件可以通过 props 和 children 进行组合: + +```tsx +function Panel({ title, children }) { + return ( +
+
{title}
+
+ {children} +
+
+ ); +} + +function App() { + return ( + +

这是一个示例面板

+ +
+ ); +} +``` + +## 下一步 + +在掌握了这些基础概念后,你可以: + +1. 深入了解[模板系统](../template-system/conditional)的使用 +2. 学习[响应式系统](../reactivity/state)的工作原理 +3. 探索[组件开发](../components/props)的最佳实践 diff --git a/docs/next/getting-started.md b/docs/next/getting-started.md new file mode 100644 index 0000000..c152221 --- /dev/null +++ b/docs/next/getting-started.md @@ -0,0 +1,143 @@ +--- +id: getting-started +title: 快速入门 +sidebar_label: 快速入门 +--- + +# 快速入门 + +欢迎使用 openInula 2!本节将带你完成环境搭建、插件集成、项目初始化和第一个组件。 + +## 环境要求 + +- Node.js >= 16.14 +- 推荐使用 npm 8+ 或 pnpm/yarn + +## 使用 CLI 快速创建项目 + +OpenInula 提供了官方脚手架工具 `create-inula`,可以一键初始化新项目,无需手动配置。 + +### 安装与使用 + +无需全局安装,直接用 npx 或 npm create: + +```bash +# 推荐方式(无需全局安装) +npm create inula@latest my-app + +# 或者使用 npx +npx create-inula my-app +``` + +执行命令后,CLI 会引导你选择项目模板、打包方式(如 webpack/vite)等,并自动拉取依赖、初始化目录结构。 + +### 典型交互流程 + +1. 选择项目名称(如 my-app) +2. 选择模板(如 Next-app,后续会有更多模板) +3. 选择打包方式(webpack 或 vite) +4. 自动安装依赖并初始化项目 + +### 目录结构示例 + +```text +my-app/ +├── src/ +│ └── main.tsx +├── public/ +│ └── index.html +├── package.json +└── ... +``` + +### 启动开发服务器 + +进入项目目录后,运行: + +```bash +npm install +npm start +``` + +即可在浏览器中访问本地开发环境。 + +### 更多用法 + +详细参数和高级用法请参考 [create-inula README](https://github.com/openinula/openinula/tree/main/packages/create-inula/README.md)。 + + +## 集成到主流构建工具 + +OpenInula 提供统一的 unplugin 插件,支持 Vite、Rollup、Webpack、Nuxt、Vue CLI、esbuild 等主流工具。 + +### 安装依赖 + + +```bash +npm i @openinula/unplugin +``` + +### Vite + +```ts +// vite.config.ts +import { defineConfig } from 'vite'; +import inulaNext from '@openinula/unplugin/vite'; + +export default defineConfig({ + plugins: [ + inulaNext({ /* 可选配置 */ }), + ], +}); +``` + +### Rollup + +```js +// rollup.config.js +import inulaNext from '@openinula/unplugin/rollup'; + +export default { + plugins: [ + inulaNext({ /* 可选配置 */ }), + ], +}; +``` + +### Webpack + +```js +// webpack.config.js +module.exports = { + /* ... */ + plugins: [ + require('@openinula/unplugin/webpack')({ /* 可选配置 */ }) + ] +} +``` + + +## 第一个组件 + +```tsx +function Hello() { + return

你好,OpenInula!

; +} +``` + +## 目录结构 + +- src/ 代码目录 +- public/ 静态资源 +- package.json 项目配置 + +## 相关链接 + +- [unplugin 官方文档](https://github.com/unjs/unplugin) +- [OpenInula 插件源码](https://github.com/openinula/openinula/tree/main/next-packages/compiler/unplugin-inula-next) +- [更多示例 playground/](https://github.com/openinula/openinula/tree/main/next-packages/compiler/unplugin-inula-next/playground) + +## 下一步 + +- 阅读[项目介绍](./introduction) +- 深入[基础概念](next/core-concepts/introduction) \ No newline at end of file diff --git a/docs/next/index.md b/docs/next/index.md new file mode 100644 index 0000000..184c8eb --- /dev/null +++ b/docs/next/index.md @@ -0,0 +1,115 @@ +--- +id: next-gen-overview +title: openInula 2 +sidebar_label: 概述 +--- +# openInula 2 + +openInula 2版本是一个现代化的 JavaScript UI 开发库,具有以下特点: + +- **无 API 设计理念**:只需使用 JSX 和原生 JavaScript 即可直观编程 +- **编译优先**:通过编译时分析优化性能 +- **轻量级响应式系统**:高效的运行时更新机制 + +## 核心特性 + +### 直接状态管理 + +```tsx +function Counter() { + let count = 0; // 直接声明状态 + + return ( +
+

计数: {count}

+ +
+ ); +} +``` + +### 内置流程控制 + +```tsx +function UserList({ users }) { + return ( +
    + + {(user) => ( + +
  • {user.name}
  • +
    + )} +
    +
+ ); +} +``` + +### 计算值 + +```tsx +function DoubleCounter() { + let count = 0; + const double = count * 2; // 自动计算的值 + + return
双倍值: {double}
; +} +``` + +### 监听系统 + +```tsx +function DataFetcher() { + let data = null; + + watch(() => { + fetch('https://api.example.com/data') + .then(res => res.json()) + .then(_data => { + data = _data; + }); + }); + + return ( +
+ +
{JSON.stringify(data, null, 2)}
+
+ +

加载中...

+
+
+ ); +} +``` + +## 快速开始 + +让我们通过以下章节来学习 openInula 2版本: + +1. [基础概念](./core-concepts/introduction) + + - 组件基础 + - JSX 语法 + - 状态管理 +2. [模板系统](./template-system/conditional) + + - 条件渲染 + - 列表渲染 + - 事件处理 +3. [响应式系统](./reactivity/state) + + - 状态声明 + - 计算值 + - 监听系统 +4. [组件开发](./components/props) + + - Props 传递 + - 组件组合 + - 上下文管理 +5. [生命周期](./lifecycle/mounting) + + - 挂载阶段 + - 卸载阶段 + - 清理操作 diff --git a/docs/next/introduction.md b/docs/next/introduction.md new file mode 100644 index 0000000..64c47a6 --- /dev/null +++ b/docs/next/introduction.md @@ -0,0 +1,41 @@ +--- +id: introduction +title: 项目介绍 +sidebar_label: 项目介绍 +--- + +# 项目介绍 + +OpenInula 是一个现代化的 JavaScript UI 框架,致力于提供极致性能和开发体验。 + +## 设计理念 + +- 无 API 哲学:只用 JSX + 原生 JS +- 编译优先:大部分逻辑编译期完成 +- 响应式:自动追踪依赖,极致性能 + +## 核心特性 + +- 直观的状态管理 +- 内置模板语法(if/for) +- 计算值与 watch +- 生命周期钩子 +- 兼容 React 生态 + +## 主要模块 + +- runtime:核心运行时 +- shared:通用工具 +- compiler:编译器 +- inula-bridge:兼容层 + +## 未来方向 + +- 更强的类型推导 +- 更丰富的生态组件 +- 更高效的编译优化 + +## 下一步 + +- [快速入门](./getting-started) +- [基础概念](next/core-concepts/introduction) \ No newline at end of file diff --git a/docs/next/lifecycle/cleanup.md b/docs/next/lifecycle/cleanup.md new file mode 100644 index 0000000..95197d7 --- /dev/null +++ b/docs/next/lifecycle/cleanup.md @@ -0,0 +1,59 @@ +--- +id: cleanup +title: 清理操作 +sidebar_label: 清理操作 +--- + +# 清理操作 + +清理操作用于移除副作用,如定时器、事件监听、watch 清理函数等。 + +## 定时器清理 + +```tsx +function Timer() { + let timer; + didMount(() => { + timer = setInterval(() => {}, 1000); + }); + willUnmount(() => clearInterval(timer)); + return
定时器已启动
; +} +``` + +## 事件监听清理 + +```tsx +function Listener() { + function onResize() {} + didMount(() => window.addEventListener('resize', onResize)); + willUnmount(() => window.removeEventListener('resize', onResize)); + return
监听窗口大小
; +} +``` + +## watch 清理函数 + +```tsx +function Watcher() { + let timer; + watch(() => { + timer = setInterval(() => {}, 1000); + return () => clearInterval(timer); + }); + return
watch 清理
; +} +``` + +## 最佳实践 + +- 所有副作用都应有清理逻辑 +- 推荐统一在 willUnmount/didUnmount 或 watch 清理函数中处理 + +## 注意事项 + +- 避免遗留定时器、事件监听等,防止内存泄漏 + +## 相关链接 + +- [卸载阶段](./unmounting) \ No newline at end of file diff --git a/docs/next/lifecycle/mounting.md b/docs/next/lifecycle/mounting.md new file mode 100644 index 0000000..7796476 --- /dev/null +++ b/docs/next/lifecycle/mounting.md @@ -0,0 +1,49 @@ +--- +id: mounting +title: 挂载阶段 +sidebar_label: 挂载阶段 +--- + +# 挂载阶段 + +组件挂载时可执行副作用,如数据请求、事件监听等。OpenInula 提供 didMount 钩子。 + +## 基本用法 + +```tsx +function PageTitle() { + let title = ''; + didMount(() => { + title = document.title; + }); + return

页面标题:{title}

; +} +``` + +## 数据请求 + +```tsx +function FetchData() { + let data = null; + didMount(() => { + fetch('/api/data') + .then(res => res.json()) + .then(d => { data = d; }); + }); + return
{JSON.stringify(data)}
; +} +``` + +## 最佳实践 + +- 只在 didMount 中执行副作用 +- 清理副作用请用 willUnmount/didUnmount + +## 注意事项 + +- didMount 只在组件首次挂载时执行一次 + +## 相关链接 + +- [卸载阶段](./unmounting) +- [清理操作](./cleanup) \ No newline at end of file diff --git a/docs/next/lifecycle/unmounting.md b/docs/next/lifecycle/unmounting.md new file mode 100644 index 0000000..4fcde87 --- /dev/null +++ b/docs/next/lifecycle/unmounting.md @@ -0,0 +1,43 @@ +--- +id: unmounting +title: 卸载阶段 +sidebar_label: 卸载阶段 +--- + +# 卸载阶段 + +组件卸载时需清理副作用,防止内存泄漏。OpenInula 提供 willUnmount/didUnmount 钩子。 + +## 基本用法 + +```tsx +function Timer() { + let timer; + didMount(() => { + timer = setInterval(() => {}, 1000); + }); + willUnmount(() => clearInterval(timer)); + // 或 didUnmount(() => clearInterval(timer)); + return
定时器已启动
; +} +``` + +## 副作用清理 + +- 清理定时器、事件监听、watch 副作用等 +- 推荐所有副作用都在卸载时清理 + +## 最佳实践 + +- 所有副作用都应在卸载时清理 +- 推荐用 willUnmount/didUnmount + +## 注意事项 + +- 清理函数只在组件卸载时执行一次 +- 避免遗留定时器、事件监听等,防止内存泄漏 + +## 相关链接 + +- [挂载阶段](./mounting) +- [清理操作](./cleanup) \ No newline at end of file diff --git a/docs/next/reactivity/computed.md b/docs/next/reactivity/computed.md new file mode 100644 index 0000000..59c545c --- /dev/null +++ b/docs/next/reactivity/computed.md @@ -0,0 +1,79 @@ +--- +id: computed +title: 计算值 +sidebar_label: 计算值 +--- + +# 计算值 + +在 openInula 2(zouyu)中,计算值(Computed)是指依赖于其他状态变量的表达式。你只需像写普通 JS 一样声明变量,编译器会自动识别并优化这些依赖关系。 + +## 基本用法 + +```tsx +function DoubleCounter() { + let count = 0; + // 计算值:double 会自动随着 count 变化 + const double = count * 2; + + return ( +
+

当前计数:{count}

+

双倍值:{double}

+ +
+ ); +} +``` + +## 多重依赖 + +计算值可以依赖多个状态变量: + +```tsx +function PriceCalculator() { + let price = 100; + let quantity = 2; + const total = price * quantity; + + return ( +
+

单价:{price}

+

数量:{quantity}

+

总价:{total}

+ +
+ ); +} +``` + +## 嵌套计算 + +计算值可以嵌套依赖其他计算值: + +```tsx +function NestedComputed() { + let a = 1; + const b = a + 2; + const c = b * 3; + + return
c 的值:{c}
; +} +``` + +## 最佳实践 + +- 只需像写普通 JS 一样声明依赖关系,编译器会自动优化。 +- 避免在计算值中产生副作用(如修改外部状态)。 +- 计算值适合用于 UI 展示、派生数据等场景。 + +## 注意事项 + +- 计算值应为纯表达式,不要在其中执行异步操作或副作用。 +- 如果依赖的状态较多,建议提前拆分变量,提升可读性。 + +## 下一步 + +- 了解[监听系统](./watch)的用法 +- 学习[状态管理](./state)的更多技巧 +- 探索[组件组合](../components/composition)的高级用法 \ No newline at end of file diff --git a/docs/next/reactivity/state.md b/docs/next/reactivity/state.md new file mode 100644 index 0000000..498b920 --- /dev/null +++ b/docs/next/reactivity/state.md @@ -0,0 +1,293 @@ +--- +id: state +title: 状态管理 +sidebar_label: 状态管理 +--- + +# 状态管理 + +OpenInula 的状态管理采用直观的方式,无需特殊的 API 或 Hook,直接使用 JavaScript 变量和赋值即可。 + +## 基础用法 + +### 声明状态 + +在组件中直接声明变量作为状态: + +```tsx +function Counter() { + let count = 0; // 直接声明状态 + + return ( +
+

计数:{count}

+ +
+ ); +} +``` + +### 对象状态 + +对于复杂的状态,可以使用对象: + +```tsx +function UserProfile() { + let user = { + name: '张三', + age: 25, + preferences: { + theme: 'dark', + language: 'zh' + } + }; + + function updateTheme(newTheme) { + user.preferences.theme = newTheme; // 直接修改对象属性 + } + + return ( +
+

{user.name}

+

年龄:{user.age}

+
+ 主题:{user.preferences.theme} + +
+
+ ); +} +``` + +### 数组状态 + +处理数组类型的状态: + +```tsx +function TodoList() { + let todos = [ + { id: 1, text: '学习 OpenInula', done: false }, + { id: 2, text: '写文档', done: false } + ]; + + function addTodo(text) { + todos = [ + ...todos, + { + id: todos.length + 1, + text, + done: false + } + ]; + } + + function toggleTodo(id) { + todos = todos.map(todo => + todo.id === id + ? { ...todo, done: !todo.done } + : todo + ); + } + + return ( +
+ +
    + + {(todo) => ( +
  • toggleTodo(todo.id)} + style={{ + textDecoration: todo.done ? 'line-through' : 'none' + }} + > + {todo.text} +
  • + )} +
    +
+
+ ); +} +``` + +## 状态更新 + +### 直接赋值 + +最简单的状态更新方式是直接赋值: + +```tsx +function Counter() { + let count = 0; + let message = ''; + + function increment() { + count++; // 直接赋值 + message = `当前计数:${count}`; // 状态之间可以互相依赖 + } + + return ( +
+

{message}

+ +
+ ); +} +``` + +### 批量更新 + +当需要同时更新多个状态时: + +```tsx +function UserForm() { + let formData = { + username: '', + email: '', + age: 0 + }; + + function resetForm() { + // 一次性更新多个字段 + formData = { + username: '', + email: '', + age: 0 + }; + } + + function updateField(field, value) { + formData[field] = value; // 更新单个字段 + } + + return ( +
+ updateField('username', e.target.value)} + /> + updateField('email', e.target.value)} + /> + updateField('age', parseInt(e.target.value))} + /> + +
+ ); +} +``` + +## 最佳实践 + +### 1. 状态初始化 + +将复杂的初始状态提取为函数: + +```tsx +function createInitialState() { + return { + user: null, + preferences: { + theme: 'light', + language: 'zh' + }, + todos: [] + }; +} + +function App() { + let state = createInitialState(); + + function reset() { + state = createInitialState(); // 重置为初始状态 + } + + return ( +
+ {/* 使用状态 */} +
+ ); +} +``` + +### 2. 状态组织 + +对于复杂组件,建议将相关状态组织在一起: + +```tsx +function ShoppingCart() { + let cart = { + items: [], + total: 0, + + addItem(product) { + this.items = [...this.items, product]; + this.updateTotal(); + }, + + removeItem(productId) { + this.items = this.items.filter(item => item.id !== productId); + this.updateTotal(); + }, + + updateTotal() { + this.total = this.items.reduce((sum, item) => sum + item.price, 0); + } + }; + + return ( +
+

购物车 ({cart.items.length})

+

总计:¥{cart.total}

+ {/* 渲染购物车内容 */} +
+ ); +} +``` + +### 3. 派生状态 + +避免重复存储可以从现有状态计算出的值: + +```tsx +function TodoApp() { + let todos = []; + + // 好的做法:通过计算获得派生状态 + const completedCount = todos.filter(todo => todo.done).length; + const remainingCount = todos.length - completedCount; + + return ( +
+

完成:{completedCount}

+

剩余:{remainingCount}

+
+ ); +} +``` + +## 注意事项 + +1. 避免直接修改复杂对象的深层属性,推荐使用不可变更新模式 +2. 确保状态更新是同步的,避免在异步操作中直接修改状态 +3. 对于大型应用,考虑使用状态管理库 +4. 注意状态的作用域,避免全局状态 + +## 下一步 + +学习完状态管理后,你可以: + +1. 了解[计算值](./computed)的使用 +2. 探索[监听系统](./watch)的功能 +3. 学习[组件组合](../components/composition)的最佳实践 \ No newline at end of file diff --git a/docs/next/reactivity/untrack.md b/docs/next/reactivity/untrack.md new file mode 100644 index 0000000..0671346 --- /dev/null +++ b/docs/next/reactivity/untrack.md @@ -0,0 +1,124 @@ +--- +id: untrack +title: untrack(非响应式访问) +sidebar_label: untrack +--- + +# untrack(非响应式访问) + +`untrack` 是 openInula 2(zouyu)响应式系统中的一个高级工具,用于在响应式计算、watch、或其他依赖收集场景下,临时"跳出"依赖追踪,获取某个状态的当前值但不建立响应式依赖。 + +## 作用 + +- **避免依赖收集**:在 computed、watch、或其他响应式回调中,某些变量的读取不希望被追踪为依赖时使用。 +- **性能优化**:减少不必要的响应式更新,避免副作用函数被频繁触发。 +- **实现只读快照**:获取某一时刻的状态快照,而不关心后续变化。 + +## 基本用法 + +```tsx +import { untrack } from 'openinula'; + +function Example() { + let count = 0; + let log = []; + + watch(() => { + // 只在 count 变化时触发 + log.push(`count: ${count}, time: ${untrack(() => Date.now())}`); + }); + + return ( +
+ +
    + {(item) =>
  • {item}
  • }
    +
+
+ ); +} +``` + +## Demo 示例 + +下面是一个最小可运行的 untrack 用法示例: + +```tsx +import { watch, untrack } from 'openinula'; + +function Demo() { + let a = 0; + let b = 0; + watch(() => { + // 只依赖 b,a 的变化不会触发 watch + console.log('sum:', untrack(() => a) + b); + }); + return ( + <> + + + + ); +} +``` +点击 a 按钮不会触发 watch,点击 b 按钮会触发 watch 并输出 sum。 + +## 在 computed 中使用 + +```tsx +import { untrack } from 'openinula'; + +function ComputedUntrack() { + let count = 0; + // 只依赖 count,不依赖 Date.now() + const message = `当前计数:${count},时间:${untrack(() => Date.now())}`; + + return
{message}
; +} +``` + +## 在 watch 中使用 + +```tsx +import { untrack } from 'openinula'; + +function WatchUntrack() { + let count = 0; + let lastTime = 0; + + watch(() => { + // 只在 count 变化时触发 + lastTime = untrack(() => Date.now()); + }); + + return ( +
+ +

上次更新时间:{lastTime}

+
+ ); +} +``` + +## 在响应式系统的其他场景 + +- **避免副作用循环**:在副作用函数中读取但不追踪依赖,防止死循环。 +- **只读快照**:在复杂计算或日志记录时,获取当前值但不建立响应式关系。 + +## 最佳实践 + +- 仅在确实不希望建立响应式依赖时使用 `untrack`。 +- 用于性能优化、避免不必要的副作用触发。 +- 保持代码可读性,避免滥用。 + +## 注意事项 + +- `untrack` 只影响其回调函数内部的依赖收集。 +- 不要在 `untrack` 内部修改响应式状态,只做只读访问。 +- 滥用 `untrack` 可能导致响应式失效,需谨慎使用。 + +## 相关链接 + +- [计算值(computed)](./computed) +- [监听系统(watch)](./watch) +- [状态管理](./state) \ No newline at end of file diff --git a/docs/next/reactivity/watch.md b/docs/next/reactivity/watch.md new file mode 100644 index 0000000..5b18460 --- /dev/null +++ b/docs/next/reactivity/watch.md @@ -0,0 +1,114 @@ +--- +id: watch +title: 监听系统 +sidebar_label: 监听系统 +--- + +# 监听系统(watch) + +openInula 2(zouyu)提供了 `watch` 方法,用于监听状态或计算值的变化,并在变化时执行副作用逻辑。 + +## 基本用法 + +```tsx +function FetchData({ url }) { + let data = null; + + watch(() => { + fetch(url) + .then(res => res.json()) + .then(_data => { + data = _data; + }); + }); + + return ( +
+ +
{JSON.stringify(data, null, 2)}
+
+ +

加载中...

+
+
+ ); +} +``` + +## 依赖追踪 + +`watch` 会自动追踪其回调中用到的所有状态和计算值,只要依赖发生变化,回调就会重新执行。 + +```tsx +function Logger() { + let count = 0; + watch(() => { + console.log('count 变化为:', count); + }); + + return ; +} +``` + +## 清理副作用 + +可以在 `watch` 回调中返回一个清理函数,用于移除定时器、事件监听等副作用: + +```tsx +function Timer() { + let time = Date.now(); + + watch(() => { + const timer = setInterval(() => { + time = Date.now(); + }, 1000); + // 返回清理函数 + return () => clearInterval(timer); + }); + + return
当前时间:{new Date(time).toLocaleTimeString()}
; +} +``` + +## 多个 watch + +可以在同一个组件中使用多个 `watch`,分别监听不同的状态: + +```tsx +function MultiWatch() { + let a = 1; + let b = 2; + + watch(() => { + console.log('a 变化:', a); + }); + watch(() => { + console.log('b 变化:', b); + }); + + return ( +
+ + +
+ ); +} +``` + +## 最佳实践 + +- 只在需要副作用(如数据请求、日志、定时器等)时使用 `watch` +- 避免在 `watch` 中直接修改依赖自身的状态,防止死循环 +- 善用清理函数,避免内存泄漏 + +## 注意事项 + +- `watch` 回调应为同步函数,避免直接返回 Promise +- 清理函数只在依赖变化或组件卸载时调用 +- 不要在 `watch` 外部直接调用副作用逻辑 + +## 下一步 + +- 学习[状态管理](./state)的更多用法 +- 了解[计算值](./computed)的高级技巧 +- 探索[生命周期](../lifecycle/mounting)的管理 \ No newline at end of file diff --git a/docs/next/template-system/conditional.md b/docs/next/template-system/conditional.md new file mode 100644 index 0000000..6fd04a6 --- /dev/null +++ b/docs/next/template-system/conditional.md @@ -0,0 +1,208 @@ +--- +id: conditional +title: 条件渲染 +sidebar_label: 条件渲染 +--- + +# 条件渲染 + +OpenInula 提供了内置的条件渲染标签,使得条件渲染变得简单直观。 + +## 基本用法 + +### if 条件 + +最简单的条件渲染使用 `if` 标签: + +```tsx +function Notification({ message }) { + return ( +
+ +

{message}

+
+
+ ); +} +``` + +### if-else 条件 + +使用 `if` 和 `else` 标签处理两种情况: + +```tsx +function LoginStatus({ isLoggedIn }) { + return ( +
+ + + + + + +
+ ); +} +``` + +### 多重条件 + +使用 `else-if` 处理多个条件: + +```tsx +function TrafficLight({ color }) { + return ( +
+ +

停止

+
+ +

注意

+
+ +

通行

+
+ +

信号灯故障

+
+
+ ); +} +``` + +## 条件表达式 + +条件表达式可以是任何返回布尔值的表达式: + +```tsx +function UserProfile({ user }) { + return ( +
+ = 18}> +

成年用户

+
+ +

高级会员

+
+
+ ); +} +``` + +## 嵌套条件 + +条件标签可以嵌套使用: + +```tsx +function ProductDisplay({ product, user }) { + return ( +
+ + + + + + + + +

请登录后购买

+
+
+ +

商品不存在

+
+
+ ); +} +``` + +## 最佳实践 + +### 1. 提前返回 + +对于简单的条件判断,可以使用提前返回模式: + +```tsx +function AdminPanel({ user }) { + if (!user) { + return

请先登录

; + } + + if (!user.isAdmin) { + return

权限不足

; + } + + return
管理员面板
; +} +``` + +### 2. 条件组合 + +对于复杂的条件逻辑,建议将条件提取为变量: + +```tsx +function UserAccess({ user, resource }) { + const canView = user && user.permissions.includes('view'); + const canEdit = canView && user.permissions.includes('edit'); + const isOwner = resource.ownerId === user.id; + + return ( +
+ +
{resource.content}
+ + + +
+ +

无访问权限

+
+
+ ); +} +``` + +### 3. 避免过度嵌套 + +当条件逻辑变得复杂时,考虑将其拆分为多个组件: + +```tsx +function UserActions({ user, resource }) { + return ( +
+ + + + + + +
+ ); +} + +function ViewPermission({ user, children }) { + return ( + + {children} + + +

无访问权限

+
+ ); +} +``` + +## 注意事项 + +1. 条件标签必须包含 `cond` 属性 +2. `else-if` 和 `else` 标签必须跟在 `if` 或 `else-if` 标签之后 +3. 条件表达式应该返回布尔值 +4. 避免在条件表达式中进行复杂的计算,建议提前计算并存储结果 + +## 下一步 + +学习完条件渲染后,你可以: + +1. 了解[列表渲染](./list-rendering)的使用方法 +2. 探索[事件处理](./event-handling)系统 +3. 学习[组件组合](../components/composition)的技巧 \ No newline at end of file diff --git a/docs/next/template-system/event-handling.md b/docs/next/template-system/event-handling.md new file mode 100644 index 0000000..4fc1bbf --- /dev/null +++ b/docs/next/template-system/event-handling.md @@ -0,0 +1,304 @@ +--- +id: event-handling +title: 事件处理 +sidebar_label: 事件处理 +--- + +# 事件处理 + +OpenInula 提供了简单直观的事件处理机制,使用 `onXxx` 属性来绑定事件处理函数。 + +## 基本用法 + +### 点击事件 + +最常用的事件处理是点击事件: + +```tsx +function ClickCounter() { + let count = 0; + + function handleClick() { + count++; + } + + return ( + + ); +} +``` + +### 内联事件处理 + +对于简单的事件处理,可以直接使用内联函数: + +```tsx +function SimpleButton() { + let message = ''; + + return ( +
+ +

{message}

+
+ ); +} +``` + +## 常用事件 + +### 表单事件 + +处理表单输入和提交: + +```tsx +function LoginForm() { + let username = ''; + let password = ''; + + function handleSubmit(e) { + e.preventDefault(); + console.log('提交登录:', { username, password }); + } + + return ( +
+ username = e.target.value} + placeholder="用户名" + /> + password = e.target.value} + placeholder="密码" + /> + +
+ ); +} +``` + +### 鼠标事件 + +处理鼠标相关事件: + +```tsx +function MouseTracker() { + let position = { x: 0, y: 0 }; + + return ( +
{ + position = { + x: e.clientX, + y: e.clientY + }; + }} + style={{ height: '200px', background: '#f0f0f0' }} + > +

鼠标位置:{position.x}, {position.y}

+
+ ); +} +``` + +### 键盘事件 + +处理键盘输入: + +```tsx +function KeyboardInput() { + let pressedKeys = []; + + return ( + { + pressedKeys = [...pressedKeys, e.key]; + }} + onKeyUp={e => { + pressedKeys = pressedKeys.filter(key => key !== e.key); + }} + placeholder="请按键..." + /> + ); +} +``` + +## 事件修饰符 + +OpenInula 支持事件修饰符来简化常见的事件处理场景: + +```tsx +function EventModifiers() { + return ( +
+ {/* 阻止默认行为 */} + e.preventDefault()} href="#"> + 阻止默认跳转 + + + {/* 停止事件传播 */} +
console.log('外层点击')}> + +
+
+ ); +} +``` + +## 自定义事件 + +在组件间通信时,可以通过 props 传递自定义事件处理函数: + +```tsx +function CustomButton({ onClick, children }) { + return ( + + ); +} + +function App() { + return ( + console.log('处理点击')}> + 点击我 + + ); +} +``` + +## 最佳实践 + +### 1. 事件处理函数命名 + +建议使用 `handle` 前缀命名事件处理函数: + +```tsx +function UserForm() { + let formData = { + name: '', + email: '' + }; + + function handleNameChange(e) { + formData.name = e.target.value; + } + + function handleEmailChange(e) { + formData.email = e.target.value; + } + + function handleSubmit(e) { + e.preventDefault(); + console.log('提交表单:', formData); + } + + return ( +
+ + + +
+ ); +} +``` + +### 2. 事件参数处理 + +在需要传递额外参数时,使用箭头函数包装: + +```tsx +function ItemList() { + const items = ['项目1', '项目2', '项目3']; + + function handleItemClick(item, index, e) { + console.log('点击项目:', item, '索引:', index, '事件:', e); + } + + return ( +
    + + {(item, index) => ( +
  • handleItemClick(item, index, e)}> + {item} +
  • + )} +
    +
+ ); +} +``` + +### 3. 性能优化 + +对于频繁触发的事件,考虑使用节流或防抖: + +```tsx +function SearchInput() { + let searchTerm = ''; + let timeoutId; + + function handleInput(e) { + // 使用防抖处理搜索 + clearTimeout(timeoutId); + timeoutId = setTimeout(() => { + searchTerm = e.target.value; + console.log('搜索:', searchTerm); + }, 300); + } + + willUnmount(() => { + clearTimeout(timeoutId); + }); + + return ( + + ); +} +``` + +## 注意事项 + +1. 事件处理函数中的 `this` 绑定 +2. 避免在渲染函数中创建内联函数 +3. 记得清理定时器和事件监听器 +4. 注意事件冒泡和捕获的顺序 + +## 下一步 + +学习完事件处理后,你可以: + +1. 探索[组件组合](../components/composition)的技巧 +2. 了解[生命周期](../lifecycle/mounting)的管理 +3. 学习[状态管理](../reactivity/state)的最佳实践 \ No newline at end of file diff --git a/docs/next/template-system/list-rendering.md b/docs/next/template-system/list-rendering.md new file mode 100644 index 0000000..c839d77 --- /dev/null +++ b/docs/next/template-system/list-rendering.md @@ -0,0 +1,257 @@ +--- +id: list-rendering +title: 列表渲染 +sidebar_label: 列表渲染 +--- + +# 列表渲染 + +OpenInula 提供了内置的 `for` 标签来实现列表渲染,使得数组的遍历和渲染变得简单直观。 + +## 基本用法 + +### 简单列表 + +使用 `for` 标签遍历数组: + +```tsx +function FruitList() { + const fruits = ['苹果', '香蕉', '橙子']; + + return ( +
    + + {(fruit) =>
  • {fruit}
  • } +
    +
+ ); +} +``` + +### 带索引的列表 + +通过第二个参数获取索引: + +```tsx +function NumberedList() { + const items = ['第一项', '第二项', '第三项']; + + return ( +
    + + {(item, index) => ( +
  • #{index + 1}: {item}
  • + )} +
    +
+ ); +} +``` + +## 对象列表 + +渲染对象数组: + +```tsx +function UserList() { + const users = [ + { id: 1, name: '张三', age: 25 }, + { id: 2, name: '李四', age: 30 }, + { id: 3, name: '王五', age: 28 } + ]; + + return ( + + + + + + + + + + + {(user) => ( + + + + + + )} + + +
ID姓名年龄
{user.id}{user.name}{user.age}
+ ); +} +``` + +## 列表操作 + +### 列表过滤 + +可以在渲染前对列表进行过滤: + +```tsx +function ActiveUserList() { + const users = [ + { id: 1, name: '张三', active: true }, + { id: 2, name: '李四', active: false }, + { id: 3, name: '王五', active: true } + ]; + + const activeUsers = users.filter(user => user.active); + + return ( +
    + + {(user) =>
  • {user.name}
  • } +
    +
+ ); +} +``` + +### 列表排序 + +可以在渲染前对列表进行排序: + +```tsx +function SortedUserList() { + const users = [ + { id: 1, name: '张三', age: 25 }, + { id: 2, name: '李四', age: 30 }, + { id: 3, name: '王五', age: 28 } + ]; + + const sortedUsers = [...users].sort((a, b) => a.age - b.age); + + return ( +
    + + {(user) => ( +
  • {user.name} ({user.age}岁)
  • + )} +
    +
+ ); +} +``` + +## 嵌套列表 + +可以嵌套使用 `for` 标签: + +```tsx +function NestedList() { + const departments = [ + { + name: '技术部', + teams: ['前端组', '后端组', '测试组'] + }, + { + name: '产品部', + teams: ['设计组', '运营组'] + } + ]; + + return ( +
+ + {(dept) => ( +
+

{dept.name}

+
    + + {(team) =>
  • {team}
  • } +
    +
+
+ )} +
+
+ ); +} +``` + +## 最佳实践 + +### 1. 列表项组件化 + +对于复杂的列表项,建议将其抽取为单独的组件: + +```tsx +function UserItem({ user }) { + return ( +
+ {user.name} +
+

{user.name}

+

{user.email}

+
+
+ ); +} + +function UserList() { + const users = [/* ... */]; + + return ( +
+ + {(user) => } + +
+ ); +} +``` + +### 2. 列表更新优化 + +当列表项可能发生变化时,建议使用不可变数据操作: + +```tsx +function TodoList() { + let todos = [ + { id: 1, text: '学习 OpenInula', done: false }, + { id: 2, text: '写文档', done: false } + ]; + + function toggleTodo(id) { + todos = todos.map(todo => + todo.id === id + ? { ...todo, done: !todo.done } + : todo + ); + } + + return ( +
    + + {(todo) => ( +
  • toggleTodo(todo.id)} + style={{ textDecoration: todo.done ? 'line-through' : 'none' }} + > + {todo.text} +
  • + )} +
    +
+ ); +} +``` + +## 注意事项 + +1. `for` 标签必须包含 `each` 属性 +2. 回调函数必须返回 JSX 元素 +3. 对于大列表,考虑使用分页或虚拟滚动 +4. 避免在渲染函数中进行复杂计算,建议提前处理数据 + +## 下一步 + +学习完列表渲染后,你可以: + +1. 了解[事件处理](./event-handling)系统 +2. 探索[组件组合](../components/composition)的技巧 +3. 学习[状态管理](../reactivity/state)的最佳实践 \ No newline at end of file diff --git a/docusaurus.config.js b/docusaurus.config.js index 8a810bf..b913490 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -73,6 +73,12 @@ const config = { position: 'left', label: 'API参考', }, + { + type: 'docSidebar', + sidebarId: 'nextSidebar', + position: 'left', + label: '下一代', + }, { href: 'https://gitee.com/openInula', label: 'Gitee', diff --git a/package-lock.json b/package-lock.json index 049d4f3..c222920 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,13 +7,18 @@ "": { "name": "openinula-doc", "version": "0.0.1", + "hasInstallScript": true, "dependencies": { "@docusaurus/core": "2.4.3", "@docusaurus/preset-classic": "2.4.3", "@easyops-cn/docusaurus-search-local": "^0.36.0", "@mdx-js/react": "^1.6.22", "clsx": "^1.2.1", - "openinula": "^0.0.1", + "cross-env": "^7.0.3", + "openinula": "0.1.2-SNAPSHOT", + "openinula-repl": "^0.0.4", + "patch-package": "^8.0.0", + "postinstall-postinstall": "^2.1.0", "prism-react-renderer": "^1.3.5", "react": "^17.0.2", "react-dom": "^17.0.2" @@ -605,17 +610,19 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -732,9 +739,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.22.16", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.16.tgz", - "integrity": "sha512-+gPfKv8UWeKKeJTUxe59+OobVcrYHETCsORl61EmSkmgymguYk/X5bp7GuUIXaFsc6y++v8ZxPsLSSuujqDphA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.1.tgz", + "integrity": "sha512-I0dZ3ZpCrJ1c04OqlNsQcKiZlsrXf/kkE4FXzID9rIOYICsAbA8mMDzhW/luRNAHdCNt7os/u8wenklZDlUVUQ==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, "bin": { "parser": "bin/babel-parser.js" }, @@ -2064,6 +2075,15 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/standalone": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/standalone/-/standalone-7.27.1.tgz", + "integrity": "sha512-HF25AUgjLNQ/xGw8TMpIwCP9QieRWi+975Jffx/0uUIhJUnsoHhIsVc5l3JbNQI+IaRQfkCTRzlAYv8JolTvBw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.22.15", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", @@ -2098,13 +2118,13 @@ } }, "node_modules/@babel/types": { - "version": "7.22.19", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.19.tgz", - "integrity": "sha512-P7LAw/LbojPzkgp5oznjE6tQEIWbp4PkkfrZDINTro9zgBRtI324/EYsiSI7lhPbpIQ+DCeR2NNmMWANGGfZsg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz", + "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==", + "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.19", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -3576,6 +3596,61 @@ "integrity": "sha512-ffTXxGIP/IRMCjuzHd6M4/HdIrw1bMfC7Bv8hMkTadnePkpe0lG0oDSdbRpSDZb2rQMAgpbWiR10BvxvNYwYrg==", "dev": true }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__standalone": { + "version": "7.1.9", + "resolved": "https://registry.npmjs.org/@types/babel__standalone/-/babel__standalone-7.1.9.tgz", + "integrity": "sha512-IcCNPLqpevUD7UpV8QB0uwQPOyoOKACFf0YtYWRHcmxcakaje4Q7dbG2+jMqxw/I8Zk0NHvEps66WwS7z/UaaA==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.6", + "@babel/types": "^7.25.6", + "@types/babel__core": "^7.20.5", + "@types/babel__generator": "^7.6.8", + "@types/babel__template": "^7.4.4", + "@types/babel__traverse": "^7.20.6" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", + "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, "node_modules/@types/body-parser": { "version": "1.19.3", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.3.tgz", @@ -4014,6 +4089,12 @@ "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" }, + "node_modules/@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "license": "BSD-2-Clause" + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -4675,12 +4756,47 @@ } }, "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4887,6 +5003,12 @@ "node": ">=8" } }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, "node_modules/clean-css": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.2.tgz", @@ -5354,6 +5476,24 @@ "node": ">=10" } }, + "node_modules/cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" + } + }, "node_modules/cross-fetch": { "version": "3.1.8", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", @@ -5728,16 +5868,20 @@ "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==" }, "node_modules/define-data-property": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.0.tgz", - "integrity": "sha512-UzGwzcjyv3OtAvolTj1GoyNYzfFR+iqbGjcnBEENZVCpM4/Ng1yhGNvS3lR/xDS74Tb2wGG9WzNSNIOS9UVb2g==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/define-lazy-prop": { @@ -5975,6 +6119,20 @@ "node": ">=8" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/duplexer": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", @@ -6069,11 +6227,41 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-module-lexer": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.1.tgz", "integrity": "sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q==" }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -6544,6 +6732,15 @@ "node": ">=8" } }, + "node_modules/find-yarn-workspace-root": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz", + "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==", + "license": "Apache-2.0", + "dependencies": { + "micromatch": "^4.0.2" + } + }, "node_modules/flux": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/flux/-/flux-4.0.4.tgz", @@ -6732,9 +6929,13 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/gensync": { "version": "1.0.0-beta.2", @@ -6745,14 +6946,24 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", - "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3" + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6763,6 +6974,19 @@ "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==" }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-stream": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", @@ -6899,11 +7123,12 @@ } }, "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dependencies": { - "get-intrinsic": "^1.1.3" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -7008,31 +7233,22 @@ } }, "node_modules/has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", "dependencies": { - "get-intrinsic": "^1.1.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "engines": { - "node": ">= 0.4" + "es-define-property": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -7048,6 +7264,18 @@ "node": ">=8" } }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/hast-to-hyperscript": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/hast-to-hyperscript/-/hast-to-hyperscript-9.0.1.tgz", @@ -7981,6 +8209,31 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, + "node_modules/json-stable-stringify": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.3.0.tgz", + "integrity": "sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "isarray": "^2.0.5", + "jsonify": "^0.0.1", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/json-stable-stringify/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "license": "MIT" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -8003,6 +8256,15 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsonify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz", + "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==", + "license": "Public Domain", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/keyv": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", @@ -8244,6 +8506,15 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/mdast-squeeze-paragraphs": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/mdast-squeeze-paragraphs/-/mdast-squeeze-paragraphs-4.0.0.tgz", @@ -8500,6 +8771,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/monaco-editor": { + "version": "0.45.0", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.45.0.tgz", + "integrity": "sha512-mjv1G1ZzfEE3k9HZN0dQ2olMdwIfaeAAjFiwNprLfYNRSz7ctv9XuCT7gPtBGrMUeV1/iZzYKj17Khu1hxoHOA==", + "license": "MIT" + }, "node_modules/mrmime": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz", @@ -8770,9 +9047,34 @@ } }, "node_modules/openinula": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/openinula/-/openinula-0.0.1.tgz", - "integrity": "sha512-wpSPpiyfpyVNJl4ppd0BT/LhdHtPw1apt4mlXuEZZw4XlCnmMeh2gQsu0/W9f+j8pED5L5fdwrrI0D+LdYyx8w==", + "version": "0.1.2-SNAPSHOT", + "resolved": "https://registry.npmjs.org/openinula/-/openinula-0.1.2-SNAPSHOT.tgz", + "integrity": "sha512-dAw3TIvyPgAgYgYevKeYHo3EvONvSyhLY3xt5YUTUFIQ60aR4Sjgc0/pbIPn1k+4M3E9iQl+x2Fx1fAjUvh7Ug==", + "license": "MulanPSL2", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/openinula-repl": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/openinula-repl/-/openinula-repl-0.0.4.tgz", + "integrity": "sha512-HE0fgfy5jOqfY462O8mGH3yeipruSecnF2qDjjEAXpQU9vTx+WwdL/iwIFEs4oR3GVG7BlsrYDmsms2Wgk27Iw==", + "dependencies": { + "@babel/standalone": "^7.23.6", + "@types/babel__standalone": "^7.1.7", + "classnames": "^2.3.2", + "monaco-editor": "^0.45.0", + "react-split-pane": "^0.1.92" + }, + "peerDependencies": { + "openinula": "0.0.0-experimental-20231201" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -8965,6 +9267,101 @@ "tslib": "^2.0.3" } }, + "node_modules/patch-package": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.0.tgz", + "integrity": "sha512-da8BVIhzjtgScwDJ2TtKsfT5JFWz1hYoBl9rUQ1f38MC2HwnEIkK8VN3dKMKcP7P7bvvgzNDbfNHtx3MsQb5vA==", + "license": "MIT", + "dependencies": { + "@yarnpkg/lockfile": "^1.1.0", + "chalk": "^4.1.2", + "ci-info": "^3.7.0", + "cross-spawn": "^7.0.3", + "find-yarn-workspace-root": "^2.0.0", + "fs-extra": "^9.0.0", + "json-stable-stringify": "^1.0.2", + "klaw-sync": "^6.0.0", + "minimist": "^1.2.6", + "open": "^7.4.2", + "rimraf": "^2.6.3", + "semver": "^7.5.3", + "slash": "^2.0.0", + "tmp": "^0.0.33", + "yaml": "^2.2.2" + }, + "bin": { + "patch-package": "index.js" + }, + "engines": { + "node": ">=14", + "npm": ">5" + } + }, + "node_modules/patch-package/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/patch-package/node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/patch-package/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/patch-package/node_modules/slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/patch-package/node_modules/yaml": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz", + "integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -9686,6 +10083,13 @@ "postcss": "^8.2.15" } }, + "node_modules/postinstall-postinstall": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/postinstall-postinstall/-/postinstall-postinstall-2.1.0.tgz", + "integrity": "sha512-7hQX6ZlZXIoRiWNrbMQaLzUUfH+sSx39u8EJ9HYuDc1kLo9IXKWjM5RSquZN1ad5GnH8CGFM78fsAAQi3OKEEQ==", + "hasInstallScript": true, + "license": "MIT" + }, "node_modules/prepend-http": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", @@ -10187,6 +10591,30 @@ "react": ">=15" } }, + "node_modules/react-split-pane": { + "version": "0.1.92", + "resolved": "https://registry.npmjs.org/react-split-pane/-/react-split-pane-0.1.92.tgz", + "integrity": "sha512-GfXP1xSzLMcLJI5BM36Vh7GgZBpy+U/X0no+VM3fxayv+p1Jly5HpMofZJraeaMl73b3hvlr+N9zJKvLB/uz9w==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.7.2", + "react-lifecycles-compat": "^3.0.4", + "react-style-proptype": "^3.2.2" + }, + "peerDependencies": { + "react": "^16.0.0-0", + "react-dom": "^16.0.0-0" + } + }, + "node_modules/react-style-proptype": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-style-proptype/-/react-style-proptype-3.2.2.tgz", + "integrity": "sha512-ywYLSjNkxKHiZOqNlso9PZByNEY+FTyh3C+7uuziK0xFXu9xzdyfHwg4S9iyiRRoPCR4k2LqaBBsWVmSBwCWYQ==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.5.4" + } + }, "node_modules/react-textarea-autosize": { "version": "8.5.3", "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.5.3.tgz", @@ -10855,12 +11283,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/search-insights": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.8.2.tgz", - "integrity": "sha512-PxA9M5Q2bpBelVvJ3oDZR8nuY00Z6qwOxL53wNpgzV28M/D6u9WUbImDckjLSILBF8F1hn/mgyuUaOPtjow4Qw==", - "peer": true - }, "node_modules/section-matter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", @@ -11099,6 +11521,23 @@ "node": ">= 0.8.0" } }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", @@ -11716,12 +12155,16 @@ "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "license": "MIT", + "dependencies": { + "os-tmpdir": "~1.0.2" + }, "engines": { - "node": ">=4" + "node": ">=0.6.0" } }, "node_modules/to-readable-stream": { @@ -11847,6 +12290,7 @@ "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/sidebars.js b/sidebars.js index 9a75dfb..de77a67 100644 --- a/sidebars.js +++ b/sidebars.js @@ -14,20 +14,47 @@ /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ const sidebars = { // By default, Docusaurus generates a sidebar from the docs folder structure - docSidebar: [{type: 'autogenerated', dirName: 'docs'}], - apiSidebar: [{type: 'autogenerated', dirName: 'apis'}], + docSidebar: [{ type: 'autogenerated', dirName: 'docs' }], + apiSidebar: [{ type: 'autogenerated', dirName: 'apis' }], // But you can create a sidebar manually - /* - tutorialSidebar: [ - 'intro', - 'hello', + nextSidebar: [ + 'next/introduction', + 'next/getting-started', + 'next/next-gen-overview', { type: 'category', - label: 'Tutorial', - items: ['tutorial-basics/create-a-document'], + label: '响应式系统', + items: ['next/reactivity/state', 'next/reactivity/computed', 'next/reactivity/watch'], + }, + { + type: 'category', + label: '模板系统', + items: [ + 'next/template-system/conditional', + 'next/template-system/list-rendering', + 'next/template-system/event-handling', + ], + }, + { + type: 'category', + label: '组件开发', + items: [ + 'next/components/props', + 'next/components/composition', + 'next/components/context', + 'next/components/ErrorBoundary', + 'next/components/Suspense', + 'next/components/Portal', + 'next/components/Dynamic', + 'next/components/Fragment', + ], + }, + { + type: 'category', + label: '生命周期', + items: ['next/lifecycle/mounting', 'next/lifecycle/unmounting', 'next/lifecycle/cleanup'], }, ], - */ }; module.exports = sidebars; diff --git a/src/css/custom.css b/src/css/custom.css index bc57fb3..6b0ad1f 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -24,7 +24,7 @@ --ifm-menu-link-padding-vertical: 16px; --ifm-footer-height: 200px; --ifm-nav-backgorund-image: linear-gradient(136deg, #4569D4FF 50%, #92B3F4FF 100%); - --ifm-menu-background-image: linear-gradient(180deg, #4569D4FF 0%, #4569D4FF 50%, #E8EEFFFF 100%); + --ifm-menu-background-image: linear-gradient(180deg, #4569D4FF 0%, #4569D4FF 100%); } /* For readability concerns, you should choose a lighter palette in dark mode. */ @@ -119,9 +119,18 @@ main .container { border-radius: 0; } -.menu__link--active { - background-image: linear-gradient(45deg, #c9daf8 0%, #9abdf5 100%) ! important; - color: #4569d4; +/* 只高亮叶子节点(非分组/非目录) */ +.menu__link--active:not(.menu__link--sublist) { + background-image: linear-gradient(45deg, #c9daf8 0%, #9abdf5 100%) !important; + color: #4569d4 !important; + font-weight: bold; +} + +/* 分组/目录(根菜单)保持原色或自定义 */ +.menu__link--active.menu__link--sublist { + background: none !important; + color: #fff !important; + font-weight: 600; } .menu__link:hover { @@ -131,6 +140,11 @@ main .container { .menu__link--active:hover { color: #4569d4; } + +.menu__link--sublist-caret:after { + filter: invert(1); +} + .theme-code-block { border: solid 1px #eee; } -- Gitee