Vue 教程
下面的内容都建立在拥有足够的Vue3 和 React-hooks 的基础上. 若还对前面叙述的不清楚的可以先了解一下
概述
taro-hooks 支持 Vue3 的方式为插件化. 使用插件模拟对应的常用 React-hooks. 当然, 如果你不想使用 taro-hooks. 但想要在 Vue3 中体验 React-hooks. 可以使用下面的插件.
配置
安装 taro-hooks
# npm
$ npm i taro-hooks
# yarn
$ yarn add taro-hooks
# pnpm
$ pnpm add taro-hooks
安装插件
首先需要下载 @taro-hooks/plugin-vue 插件
# npm
$ npm i @taro-hooks/plugin-vue
# yarn
$ yarn add @taro-hooks/plugin-vue
# pnpm
$ pnpm add @taro-hooks/plugin-vue
建议您同时下载 @taro-hooks/shared 插件用于转换一些等同状态的 state
# npm
$ npm i @taro-hooks/shared
# yarn
$ yarn add @taro-hooks/shared
# pnpm
$ pnpm add @taro-hooks/shared
项目配置
const config = {
// ...
plugins: ['@taro-hooks/plugin-vue'],
// ...
};
我们也配套提供了适配
unplugin-auto-import
的插件@taro-hooks/plugin-auto-import
注意: 插件内部会检测当前项目的框架和依赖状态, 若您的项目不是 Vue3 的项目, 那么很可能无法正常启动.
Hooks
所有的 Hooks 均在运行时注入到了 @taro-hooks/core 内部, 并使用了和 React 中一致的名称.
<script>
import {
useState,
useEffect,
useRef,
useReducer,
useCallback,
useMemo,
useLayoutEffect,
useContext,
useWatchEffect,
createContext,
} from '@taro-hooks/core';
export default {
setup() {
// use hooks in setup
},
};
</script>
下面的 Hook 使用示例尽可能的和 React 教程保持了一致. 方便 React 开发的同学阅读
useState
useState 帮助您快速创建一个响应式数据和更改它的方法 但这里需要注意的是, 它内部使用的是 Ref. 在 template 内会被自动解包. 但在 setup 内使用依然需要显式的使用 state.value 来获取值. 当然我们也提供了一个工具来抹平差异. 在下面的示例中有体现
<template>
<block>
<demo-content title="1. Counter(number)">
<nut-button shape="square" type="primary" block @click="handleClick()">{{
count
}}</nut-button>
</demo-content>
<demo-content title="2. Text field(string)">
<control-input :value="text" @input="handleChange($event)" />
<view class="control-input">You typed: {{ text }}</view>
<nut-button
shape="square"
type="danger"
block
@click="handleChange('hello')"
>Reset</nut-button
>
</demo-content>
<demo-content title="3. Checkbox is not well. use toggle instand(boolean)">
<view class="control-input"
>You {{ liked ? 'liked' : 'not liked' }} this!</view
>
<nut-button
shape="square"
type="primary"
block
@click="handleChangeLiked()"
>click here for like or not like!</nut-button
>
</demo-content>
<demo-content title="4. Form(two variables) above all."></demo-content>
</block>
</template>
<script>
import { useState } from '@taro-hooks/core';
import { escapeState } from '@taro-hooks/shared';
export default {
setup() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(escapeState(count) + 1);
}
const [text, setText] = useState('hello');
function handleChange(val) {
setText(val);
}
const [liked, setLiked] = useState(true);
function handleChangeLiked() {
setLiked(!escapeState(liked));
}
return {
handleClick,
count,
handleChange,
text,
handleChangeLiked,
liked,
};
},
};
</script>
注意: useEffect 、 useWatchEffect 、 useRef、 useCallback、 useMemo 内部均使用 watchEffect 收集依赖变化. 它满足 与watch共享的行为. 应尽量避免在 hooks 外单独处理依赖清除
useEffect
useEffect 帮助您给函数组件增加操作副作用的能力. 它和 componentDidMount|DidUpdate|WillUnmount 具有相同的用途.
<template>
<block>
<demo-content title="1. without sideEffect">
<nut-button shape="square" type="primary" block @click="handleClick()">{{
count
}}</nut-button>
</demo-content>
<demo-content title="2. with sideEffect">
<view class="control-input">{{
isOnline === null ? 'Loading...' : isOnline ? 'Online' : 'Offline'
}}</view>
</demo-content>
</block>
</template>
<script lang="ts">
import { showToast } from '@tarojs/taro';
import { useState, useEffect } from '@taro-hooks/core';
import { escapeState } from '@taro-hooks/shared';
const subsQueue = {};
async function subscribeToFriendStatus(id, statusChange) {
if (subsQueue[id]) {
console.warn('alert: already subscribed to friend status', id);
return;
}
subsQueue[id] = setInterval(
() => statusChange(Boolean(Math.random() > 0.5)),
1000,
);
}
async function unsubscribeFromFriendStatus(id) {
if (!subsQueue[id]) {
console.warn('alert: not subscribed to friend status', id);
return;
}
clearInterval(subsQueue[id]);
delete subsQueue[id];
}
const chatAPI = {
subscribeToFriendStatus,
unsubscribeFromFriendStatus,
};
export default {
setup() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(escapeState(count) + 1);
}
useEffect(() => {
showToast({
title: 'You clicked' + escapeState(count) + 'times',
duration: 2000,
});
}, [count]);
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
chatAPI.subscribeToFriendStatus(1, setIsOnline);
return function cleanup() {
chatAPI.unsubscribeFromFriendStatus(1);
};
}, [isOnline]);
return {
handleClick,
count,
isOnline,
};
},
};
</script>
useRef
useRef 帮助您创建一个不会触发重新渲染的引用. 它和 React.createRef 具有相同的用途.
<template>
<block>
<demo-content title="1. click counter">
<nut-button shape="square" type="primary" block @click="handleClick()"
>Click me!</nut-button
>
</demo-content>
<demo-content title="1. click counter">
<view class="control-input">{{
startTime != null && now != null ? (now - startTime) / 1000 : 0
}}</view>
<nut-row type="flex" :gutter="4">
<nut-col
><nut-button shape="square" type="info" block @click="handleStart()"
>Start!</nut-button
></nut-col
>
<nut-col
><nut-button shape="square" type="primary" block @click="handleStop()"
>Stop!</nut-button
></nut-col
>
</nut-row>
</demo-content>
</block>
</template>
<script>
import { useRef, useState } from '@taro-hooks/core';
import { showToast } from '@tarojs/taro';
export default {
setup() {
const ref = useRef(0);
function handleClick() {
ref.current = ref.current + 1;
showToast({
title: 'You clicked' + ref.current + 'times',
duration: 2000,
});
}
const [startTime, setStartTime] = useState(null);
const [now, setNow] = useState(null);
const intervalRef = useRef(null);
function handleStart() {
setStartTime(Date.now());
setNow(Date.now());
clearInterval(intervalRef.current);
intervalRef.current = setInterval(() => {
setNow(Date.now());
}, 10);
}
function handleStop() {
clearInterval(intervalRef.current);
}
return {
handleClick,
handleStart,
handleStop,
startTime,
now,
};
},
};
</script>
注意useRef内部是默认使用reactive来生成. 若需要在视图内保活, 请不要解构.
useReducer
useReducer 帮助您创建一个 reducer (eventBus) 在组件中使用.
<template>
<block>
<demo-content title="1. Form (Object)">
<control-input
@input="handleNameChange($event)"
:value="formState.name"
/>
<view class="control-input"
>Hello, {{ formState.name }}. You are {{ formState.age }}.</view
>
<nut-button
shape="square"
type="primary"
block
@click="handleIncrementAge()"
>Increment Age</nut-button
>
</demo-content>
<demo-content title="2. Todo list (Array)">
<nut-row type="flex" :gutter="4">
<nut-col :span="18">
<control-input
placeholder="Add task"
:value="currentAddTask"
@input="setCurrentAddTask($event)"
/>
</nut-col>
<nut-col :span="6">
<nut-button
shape="square"
type="info"
block
@click="handleAddTask(currentAddTask)"
>Add</nut-button
>
</nut-col>
</nut-row>
<ListItem
v-for="item in tasks"
:key="item.id"
:done="item.done"
:text="item.text"
:id="item.id"
@taskChange="handleChangeTask($event)"
@taskDelete="handleDeleteTask($event)"
/>
</demo-content>
<demo-content
title="3. Writing concise update logic with Immer. write self"
/>
</block>
</template>
<script lang="ts">
import { useState, useReducer } from '@taro-hooks/core';
import Item from './Item.vue';
// 1. Form (Object)
function formReducer(state, action) {
switch (action.type) {
case 'incremented_age': {
return {
name: state.name,
age: state.age + 1,
};
}
case 'changed_name': {
return {
name: action.nextName,
age: state.age,
};
}
}
throw Error('Unknown action: ' + action.type);
}
const initialFormState = { name: 'Taylor', age: 42 };
// 2. Todo list (Array)
function tasksReducer(tasks, action) {
switch (action.type) {
case 'added': {
return [
...tasks,
{
id: action.id,
text: action.text,
done: false,
},
];
}
case 'changed': {
return tasks.map((t) => {
if (t.id === action.task.id) {
return action.task;
} else {
return t;
}
});
}
case 'deleted': {
return tasks.filter((t) => t.id !== action.id);
}
default: {
throw Error('Unknown action: ' + action.type);
}
}
}
const initialTaskState = [
{ id: 0, text: 'Visit Kafka Museum', done: true },
{ id: 1, text: 'Watch a puppet show', done: false },
{ id: 2, text: 'Lennon Wall pic', done: false },
];
let nextId = 3;
export default {
components: {
ListItem: Item,
},
setup() {
// 1. Form (Object)
const [formState, formDispatch] = useReducer(formReducer, initialFormState);
const handleIncrementAge = () => {
formDispatch({ type: 'incremented_age' });
};
const handleNameChange = (e) => {
formDispatch({ type: 'changed_name', nextName: e });
};
// 2. Todo list (Array)
const [tasks, taskDispatch] = useReducer(tasksReducer, initialTaskState);
function handleAddTask(text) {
taskDispatch({
type: 'added',
id: nextId++,
text: text,
});
setCurrentAddTask(null);
}
function handleChangeTask(task) {
taskDispatch({
type: 'changed',
task: task,
});
}
function handleDeleteTask(taskId) {
taskDispatch({
type: 'deleted',
id: taskId,
});
}
const [currentAddTask, setCurrentAddTask] = useState(null);
return {
formState,
handleIncrementAge,
handleNameChange,
tasks,
handleAddTask,
handleChangeTask,
handleDeleteTask,
currentAddTask,
setCurrentAddTask,
};
},
};
</script>
useCallback
useCallback 返回一个 memoized 回调函数
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
useMemo
useMemo 返回一个 memoized 回调函数
const memoizedValue = useMemo(
() => computeExpensiveValue(a, b),
[a, b]
);
你可以把 useMemo 作为性能优化的手段,但不要把它当成语义上的保证
useLayoutEffect
useLayoutEffect 函数签名和 useEffect 相同. 但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染
请尽可能的减少使用此 hook 的次数,因为它会导致额外的性能开销
useContext
useContext 帮助您读取和订阅组件的上下文.
<template>
<ThemeProvider :value="theme">
<UserProvider :value="userProviderValue">
<block>
<demo-content title="attention: this example is a multiple contexts">
<ThemeContent />
</demo-content>
<demo-content title="1. Updating a value via context">
<nut-checkbox v-model="memoTheme" @change="handleChange"
>Use dark mode</nut-checkbox
>
</demo-content>
<demo-content title="2. Updating an object via context" />
</block>
</UserProvider>
</ThemeProvider>
</template>
<script lang="ts">
import { escapeState } from '@taro-hooks/shared';
import { useState, useContext, useMemo } from '@taro-hooks/core';
import ThemeContent from './ThemeContent.vue';
import { themeContext, userContext } from './context';
const { Provider: ThemeProvider } = themeContext;
const { Provider: UserProvider } = userContext;
export default {
components: {
ThemeProvider,
UserProvider,
ThemeContent,
},
setup() {
// 1. Updating a value via context
const [theme, setTheme] = useState({ theme: 'light' });
const currentTheme = useContext(themeContext);
const handleChange = (event) => {
setTheme({ theme: event ? 'dark' : 'light' });
};
const memoTheme = useMemo(() => {
return escapeState(theme).theme === 'dark';
}, [theme]);
// 2. Updating an object via context
const [user, setUser] = useState({ name: null });
const userProviderValue = {
user,
setUser,
};
return {
theme,
currentTheme,
setTheme,
handleChange,
userProviderValue,
memoTheme,
};
},
};
</script>
注意: context 必须设置为对象的形式. 为了达到多层继承的目的. 继承的动作由 createContext
来完成. 内部实则走的是扩展合并.