Composition API
- createApp:创建一个vue对象。
- setup: composition api的入口。
- reactive: 把一个对象变成响应式对象,并且把该对象的嵌套属性也变成响应式对象。
vue3.0 响应式对象不能解构
案例:获取鼠标位置
import { createApp, reactive } from 'vue'
// setup:第一个参数:props
// 第二个参数:context,attrs,emit,slots
createApp({
setup() {
// 此时声明的变量不是响应式的。加了reactive之后把对象变成响应式对象。
const position = reactive({
x: 0,
y: 0,
})
return {
position
}
},
mounted() {
// 在生命周期的钩子函数中使用position:this.position
this.position.x = 100
}
})
生命周期钩子函数
- 如何在setup中使用生命周期钩子函数?
on + 生命周期第一个字母大写:
<div id="app"></div>
<script type="module">
import { createApp, reactive, onMounted,unonMounted } from 'vue'
function useMousePosition() {
const position = reactive({
x: 0,
y: 0,
})
// 封装的函数,用来更新x,y的值
const update = (e) => {
position.x = e.pageX
position.y = e.pageY
}
onMounted(() => {
window.addEventListener('mousemove', update)
})
unonMounted(() => {
window.addEventListener('mousemove', update)
})
return position
}
createApp({
setup() {
const position = useMousePosition()
return {
position
}
},
})
app.mount('#app')
</script>
reactive, toRefs, ref 创建响应式数据
toRefs:传入的是代理的对象。 把传入的代理的对象的所有属性都变成响应式对象。toRefs返回的对象解构的变量是响应式的。
function useMousePosition() {
const position = reactive({
x: 0,
y: 0,
})
// 封装的函数,用来更新x,y的值
const update = (e) => {
position.x = e.pageX
position.y = e.pageY
}
onMounted(() => {
window.addEventListener('mousemove', update)
})
unonMounted(() => {
window.addEventListener('mousemove', update)
})
return toRefs(position)
}
ref:把普通数据转成响应式数据对象。
ref内部做了什么?传入的参数如果是对象,那么内部调用的就是reactive,如果是基本类型的值,那么内部会创建一个具有value属性的对象,该对象的value属性具有gutter和setter,在gutter搜集依赖,在setter中触发更新。
function useCount() {
// ref:基本数据转成响应式对象,它的值是value
const count = ref(0)
return {
count,
increase: () => {
count.value ++
}
}
}
createApp({
setup() {
return {
...useCount()
}
}
})
computed(计算属性)
可以缓存计算的结果,当属性发生变化时再重新计算。
第一种用法:
computed(()=>count.value + 1)
第2种用法:
const count = ref(1)
const plusOne = computed({
get: () => count.value + 1,
set: () => {
count.value = val - 1
}
})
案例:
import {computed} from 'vue'
{{activeCount}}
<div @click="plus">+ </div>
const data = [
{ text: '看书', computed: false },
{ text: '敲代码', computed: true },
{ text: '约会', computed: false },
]
createApp({
setup() {
const todos = reactive(data)
const activeCount = computed(() => {
return todos.filter(item => !item.computed).length
})
return {
activeCount,
plus: () => {
todos.push({text: '开会', computed: false})
}
}
}
})
watch
创建监听器:监听响应式数据的变化,然后执行相应的回调函数。
watch的三个参数:
deep:深入监听。
immediate:立即执行。
案例:
createApp() {
setup() {
const question = ref('')
const answer = ref('')
watch(question, async(newValue, oldValue) {
const response = await fetch('https://www.yesno.wtf/api')
const data = await response.json()
answer.value = data.value
})
return {question, answer}
}
}
watchEffect
watchEffect:是watch函数的简化版本,也用来监听数据的变化。
接收一个函数作为参数,监听函数内响应式数据的变化。
watchEffect:返回值是:取消函数的监听。
案例:
createApp() {
setup() {
const count = ref(0)
const stop = watchEffect(() => {
console.log(count.value)
})
return {count, stop, increate:() => {count.value++}}
}
}
todolist案例:(待办事项清单)
添加待办事项
vue-cli:4.5版本以上
vue:v3.0以上
<input
class="new-todo"
placeholder="What needs to be done?"
autocomplete="off"
autofocus
v-model="input"
@keyup.enter="addTodo"
>
<ul class="todo-list">
<li
v-for="todo in todos"
:key="todo"
>
<div class="view">
<!-- 完成代办事情 -->
<input class="toggle" type="checkbox" v-model="todo.completed">
<label>{{ todo.text }}</label>
<!-- 删除 -->
<button class="destroy" @click=""></button>
</div>
<!-- 编辑 -->
<input
class="edit"
type="text"
v-model="todo.text"
>
</li>
</ul>
<script>
import './essets/index.css'
import { ref } from 'vue'
// 拆分代码,方便后续维护
// 1。添加代办事项
const useAdd = todos => {
const input = ref('')
const addTodo = (todos) => {
const text = input.value && input.value.trim() //input.value: 获取input的值
if (text.length === 0) return
todos.value.unshift({ text, complated: false })
// 添加完成,清空文本框
input.value = ''
}
return {
input,
addTodo,
}
}
export default {
name: 'App',
setup() {
const todos = ref([])
return {
todos,
...useAdd(todos),
}
}
}
</script>
删除待办事项
<input
class="new-todo"
placeholder="What needs to be done?"
autocomplete="off"
autofocus
v-model="input"
@keyup.enter="addTodo"
>
<ul class="todo-list">
<li
v-for="todo in todos"
:key="todo"
>
<div class="view">
<!-- 完成代办事情 -->
<input class="toggle" type="checkbox" v-model="todo.completed">
<label>{{ todo.text }}</label>
<!-- 删除 -->
+ <button class="destroy" @click="remove(todo)"></button>
</div>
<!-- 编辑 -->
<input
class="edit"
type="text"
v-model="todo.text"
>
</li>
</ul>
<script>
// 2.删除代办事项: 注意传递的参数,
const useRemove = (todos) => {
const remove = (todo) => {
const index = todos.value.indexOf(todo)
todos.value.splice(index, 1)
}
return {
remove
}
}
export default {
name: 'App',
setup() {
const todos = ref([])
return {
todos,
...useRemove(todos),
}
}
}
</script>
编辑待办事项
<section class="main" v-show="count">
<input id="toggle-all" class="toggle-all" type="checkbox">
<label for="toggle-all">Mark all as complete</label>
<ul class="todo-list">
<!-- editing:是否是编辑状态 -->
<li
v-for="todo in filteredTodos"
:key="todo"
:class="{ editing: todo === editingTodo, completed: todo.completed }"
>
<div class="view">
<!-- 完成代办事情 -->
<input class="toggle" type="checkbox">
<!-- dblclick 双击事件 -->
<label @dblclick="editTodo(todo)">{{ todo.text }}</label>
<!-- 删除 -->
<button class="destroy" @click="remove(todo)"></button>
</div>
<!--
@keyup.enter:按下回车,修改数据
@blur:失去焦点,完成编辑
@keyup.esc:取消编辑
v-editing-focus="todo === editingTodo":获取焦点
-->
<input
class="edit"
type="text"
v-editing-focus="todo === editingTodo"
v-model="todo.text"
@keyup.enter="doneEdit(todo)"
@blur="doneEdit(todo)"
@keyup.esc="cancelEdit(todo)"
>
</li>
</ul>
</section>
<script>
// 3.编辑代办事项 1.双击显示文本框(input) 2.按enter/input失去焦点,修改数据 3.按esc可以取消编辑 4.清空文本框的内容,按enter,删除 5.显示input的时候获取焦点
+ const useEdit = (remove) => {
// 1.双击显示文本框(input)
let beforeEditingText = '' // 编辑之前的文本
const editingTodo = ref(null) // 编辑的数据
const editingTodo = todo => {
beforeEditingText = todo.text
editingTodo.value = todo
}
// 2.按enter/input失去焦点,修改数据
const doneEdit = (todo) => {
if (!editingTodo.value) return
todo.text = todo.text.trim()
todo.text || remove(todo)
editingTodo.value = ''
}
// 按esc可以取消编辑
const calcelEdit = (todo) => {
editingTodo.value = null
todo.text = beforeEditingText
}
return {
editingTodo,
editingTodo,
doneEdit,
calcelEdit
}
}
export default {
name: 'App',
setup() {
const todos = ref([])
+ const remove = useRemove(todos)
return {
todos,
+ ...useEdit(remove)
}
}
}
</script>
编辑待办事项-编辑文本框获取焦点
自定义指令:编辑文本框获取焦点(v-editing-focus)
参数:boolean:true,正在编辑的文本框
<script>
export default {
name: 'App',
+ directives: {
editingFocus: (el, binding) => {
binding.value && el.focus()
}
}
}
</script>
使用:
<input
class="edit"
type="text"
+ v-editing-focus="todo === editingTodo"
v-model="todo.text"
>
改变待办事项完成状态
```javascript
<section class="main" v-show="count">
+ <input id="toggle-all" class="toggle-all" v-model="allDone" type="checkbox">
<label for="toggle-all">Mark all as complete</label>
<ul class="todo-list">
<!-- editing:是否是编辑状态, completed:当前代办事项是否完成 -->
<li
v-for="todo in filteredTodos"
:key="todo"
:class="{ editing: todo === editingTodo, completed: todo.completed }"
>
<div class="view">
<!-- 完成代办事情 -->
+ <input class="toggle" type="checkbox" v-model="todo.completed">
<!-- dblclick 双击事件 -->
<label @dblclick="editTodo(todo)">{{ todo.text }}</label>
<!-- 删除 -->
<button class="destroy" @click="remove(todo)"></button>
</div>
<!--
@keyup.enter:按下回车,修改数据
@blur:失去焦点,完成编辑
@keyup.esc:取消编辑
v-editing-focus="todo === editingTodo":获取焦点
-->
<input
class="edit"
type="text"
v-editing-focus="todo === editingTodo"
v-model="todo.text"
@keyup.enter="doneEdit(todo)"
@blur="doneEdit(todo)"
@keyup.esc="cancelEdit(todo)"
>
</li>
</ul>
</section>
<script>
// 4.切换代办项完成状态
const useFilter = todos => {
const allDone = computed({
get() {
return !todos.value.filter((todo) => todo.completed).length
},
set(value) {
todos.value.forEach(todo => {
todo.complated = value
});
}
})
return {
allDone
}
}
</script>
export default {
name: 'App',
setup() {
const todos = ref([])
return {
todos,
...useFilter(todos)
}
}
}
切换代办事项状态
<footer class="footer" v-show="count">
<span class="todo-count">
<strong>1</strong> item left
</span>
<ul class="filters">
<li><a href="#/all">All</a></li>
<li><a href="#/active">Active</a></li>
<li><a href="#/completed">Completed</a></li>
</ul>
<button class="clear-completed" >
Clear completed
</button>
</footer>
// 4. 切换待办项完成状态
const useFilter = todos => {
const allDone = computed({
get () {
return !todos.value.filter(todo => !todo.completed).length
},
set (value) {
todos.value.forEach(todo => {
todo.completed = value
})
}
})
+ const filter = {
all: list => list, // 所有的数据
active: list => list.filter(todo => !todo.completed), // 未完成事项
completed: list => list.filter(todo => todo.completed) // 已经过完成的代办事项
}
// 事项类型,初始值为all,
+ const type = ref('all')
+ const filteredTodos = computed(() => filter[type.value](todos.value)) // 过滤之后的数据
+ const count = computed(() => todos.value.length)
const onHashChange = () => {
// 获取路径中#之后的字符
const hash = window.location.hash.replace('#/', '')
if (filter[hash]) {
type.value = hash
} else {
// 加载所有的数据
type.value = 'all'
// 地址为空
window.location.hash = ''
}
}
// 注册 hashchange事件
+ onMounted(() => {
window.addEventListener('hashchange', onHashChange)
// 调用onHashChange事件加载数据
onHashChange()
})
// 移除 hashchange事件
+ onUnmounted(() => {
window.removeEventListener('hashchange', onHashChange)
})
return {
allDone,
count,
filteredTodos,
}
}
其它
<footer class="footer" v-show="count">
<span class="todo-count">
+ <strong>{{ remainingCount }}</strong> {{ remainingCount > 1 ? 'items' : 'item' }} left
</span>
<ul class="filters">
<li><a href="#/all">All</a></li>
<li><a href="#/active">Active</a></li>
<li><a href="#/completed">Completed</a></li>
</ul>
+ <button class="clear-completed" @click="removeCompleted" v-show="count > remainingCount">
Clear completed
</button>
</footer>
// 2. 删除待办事项
const useRemove = todos => {
const remove = todo => {
const index = todos.value.indexOf(todo)
todos.value.splice(index, 1)
}
// 删除已完成的代办事项
+ const removeCompleted = () => {
todos.value = todos.value.filter(todo => !todo.completed)
}
return {
remove,
removeCompleted
}
}
// 4. 切换待办项完成状态
const useFilter = todos => {
const allDone = computed({
get () {
return !todos.value.filter(todo => !todo.completed).length
},
set (value) {
todos.value.forEach(todo => {
todo.completed = value
})
}
})
const filter = {
all: list => list, // 所有的数据
active: list => list.filter(todo => !todo.completed), // 未完成事项
completed: list => list.filter(todo => todo.completed) // 已经过完成的代办事项
}
// 事项类型,初始值为all,
const type = ref('all')
const filteredTodos = computed(() => filter[type.value](todos.value)) // 过滤之后的数据
+ const remainingCount = computed(() => filter.active(todos.value).length) // 显示未完成代办事项的的个数
const count = computed(() => todos.value.length)
const onHashChange = () => {
// 获取路径中#之后的字符
const hash = window.location.hash.replace('#/', '')
if (filter[hash]) {
type.value = hash
} else {
// 加载所有的数据
type.value = 'all'
// 地址为空
window.location.hash = ''
}
}
// 注册 hashchange事件
onMounted(() => {
window.addEventListener('hashchange', onHashChange)
// 调用onHashChange事件加载数据
onHashChange()
})
// 移除 hashchange事件
onUnmounted(() => {
window.removeEventListener('hashchange', onHashChange)
})
return {
allDone,
count,
filteredTodos,
+ remainingCount
}
}
存储代办事项
数据存储到localStorage中
utils/useLocalStorage.js
// 字符串转换成对象
function parse (str) {
let value // value转换之后的结果
try {
value = JSON.parse(str)
} catch {
value = null
}
return value
}
// 对象转为字符串
function stringify (obj) {
let value // value转换之后的结果
try {
value = JSON.stringify(obj)
} catch {
value = null
}
return value
}
export default function useLocalStorage () {
function setItem (key, value) {
value = stringify(value)
window.localStorage.setItem(key, value)
}
function getItem (key) {
let value = window.localStorage.getItem(key)
if (value) {
value = parse(value)
}
return value
}
return {
setItem,
getItem
}
}
import useLocalStorage from './utils/useLocalStorage'
const storage = useLocalStorage()
<script>
// 5. 存储待办事项
const useStorage = () => {
const KEY = 'TODOKEYS'
const todos = ref(storage.getItem(KEY) || [])
//watchEffect: 可以监听到数据的变化
watchEffect(() => {
storage.setItem(KEY, todos.value)
})
return todos
}
export default {
name: 'App',
setup () {
const todos = useStorage()
}
}
</script>