跳到主要内容

Vue 教程

警告

下面的内容都建立在拥有足够的Vue3React-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

项目配置

config/index.js
const config = {
// ...
plugins: ['@taro-hooks/plugin-vue'],
// ...
};

我们也配套提供了适配 unplugin-auto-import 的插件 @taro-hooks/plugin-auto-import

注意: 插件内部会检测当前项目的框架和依赖状态, 若您的项目不是 Vue3 的项目, 那么很可能无法正常启动.

Hooks

所有的 Hooks 均在运行时注入到了 @taro-hooks/core 内部, 并使用了和 React 中一致的名称.

example/index.vue
<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 来获取值. 当然我们也提供了一个工具来抹平差异. 在下面的示例中有体现

/useState/index.vue
<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>
警告

注意: useEffectuseWatchEffectuseRefuseCallbackuseMemo 内部均使用 watchEffect 收集依赖变化. 它满足 与watch共享的行为. 应尽量避免在 hooks 外单独处理依赖清除

useEffect

useEffect 帮助您给函数组件增加操作副作用的能力. 它和 componentDidMount|DidUpdate|WillUnmount 具有相同的用途.

/useEffect/index.vue
<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 具有相同的用途.

/useRef/index.vue
<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>

useReducer

useReducer 帮助您创建一个 reducer (eventBus) 在组件中使用.

/useReducer/index.vue
<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 回调函数

/useCallback/index.vue
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);

useMemo

useMemo 返回一个 memoized 回调函数

/useMemo/index.vue
const memoizedValue = useMemo(
() => computeExpensiveValue(a, b),
[a, b]
);

useLayoutEffect

useLayoutEffect 函数签名和 useEffect 相同. 但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染

useContext

useContext 帮助您读取和订阅组件的上下文.

/useContext/index.vue
<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>