本文为拉勾网大前端高薪训练营第一期笔记
心得体会
Vue3.0的重要变动就是composition API,在查看代码方面比之前真的好太多了,感觉起来和React.js的Hook很类似。
composition api举例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
x: {{ x }} <br>
y: {{ y }}
</div>
<script type="module">
import { createApp, reactive, onMounted, onUnmounted, toRefs } from './node_modules/vue/dist/vue.esm-browser.js'
function useMousePosition () {
// 第一个参数 props
// 第二个参数 context:{attrs、emit、slots}
const position = reactive({
x: 0,
y: 0
})
const update = e => {
position.x = e.pageX
position.y = e.pageY
}
onMounted(() => {
window.addEventListener('mousemove', update)
})
onUnmounted(() => {
window.removeEventListener('mousemove', update)
})
return toRefs(position)
}
const app = createApp({
setup () { //setup的返回值能用在组件其他位置获得,比如mounted
// const position = useMousePosition()
const { x, y } = useMousePosition()
return {
x,
y
}
}
})
console.log(app)
app.mount('#app')
</script>
</body>
</html>
生命周期钩子函数
Reactive toRefs ref
直接解构useMousePosition返回的Position,会用新变量去接收x y从而失去响应式特性,需要在返回时包裹toRefs(position)
用了toRefs,将传入的参数的所有属性的值都转换成响应式对象,每个都自带value属性,在模板里用可以省略.value取值
const position = useMousePosition()
const { x, y } = useMousePosition()
ref的使用
ref是把输入的参数包装成响应式对象,包括proxy监听getter setter
如果ref参数是对象,那内部会调用reactive包装这个参数对象,如果参数是基本类型,比如是0,会创建有value属性的对象,这个value属性有个getter setter,getter里收集依赖,setter触发更新
此时count.value不可省略,因为不是在模板里
如果是在模板里.value可以省略
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<button @click="increase">按钮</button>
<span>{{ count }}</span>
</div>
<script type="module">
import { createApp, ref } from './node_modules/vue/dist/vue.esm-browser.js'
function useCount () {
const count = ref(0)
return {
count,
increase: () => {
count.value++
}
}
}
createApp({
setup () {
return {
...useCount()
}
}
}).mount('#app')
</script>
</body>
</html>
- reactive:把对象转换成响应式对象,是一个代理对象
- ref:把基本类型数据转换成响应式对象
- toRefs:把一个对象里的所有属性转换成响应式对象,处理对象属性的时候类似ref,通过toRefs处理reactive返回的代理对象,可以进行解构操作
computed
两种用法
用法1
watch(()=>count.value+1)
用法2
const count = ref(1)
const plusOne = computed({
get: ()=> count.value + 1,
set: value => {
count.value = val - 1
}
})
举例
<body>
<div id="app">
<button @click="push">按钮</button>
未完成:{{ activeCount }}
</div>
<script type="module">
import { createApp, reactive, computed } from './node_modules/vue/dist/vue.esm-browser.js'
const data = [
{ text: '看书', completed: false },
{ text: '敲代码', completed: false },
{ text: '约会', completed: true }
]
createApp({
setup () {
const todos = reactive(data)
const activeCount = computed(() => {
return todos.filter(item => !item.completed).length
})
return {
activeCount,
push: () => {
todos.push({
text: '开会',
completed: false
})
}
}
}
}).mount('#app')
</script>
</body>
watch
watch三个参数
- 要监听的数据
- 监听到数据变化后执行的函数,这个函数有两个参数分别是新值和旧值
- 选项对象, deep和immediate
watch返回值是取消监听的函数
彩蛋:www.yesno.wtf/api 随机回答是或否
举例
<body>
<div id="app">
<p>
请问一个 yes/no 的问题:
<input v-model="question">
</p>
<p>{{ answer }}</p>
</div>
<script type="module">
// https://www.yesno.wtf/api
import { createApp, ref, watch } from './node_modules/vue/dist/vue.esm-browser.js'
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.answer
})
return {
question,
answer
}
}
}).mount('#app')
</script>
</body>
WatchEffect
watch函数的简化版本,也用来监视数据变化
接收一个函数作为参数,监听函数内响应式数据的变化
返回一个取消监听的函数,一旦执行,watchEffect失效
举例
<body>
<div id="app">
<button @click="increase">increase</button>
<button @click="stop">stop</button>
<br>
{{ count }}
</div>
<script type="module">
import { createApp, ref, watchEffect } from './node_modules/vue/dist/vue.esm-browser.js'
createApp({
setup () {
const count = ref(0)
const stop = watchEffect(() => {
console.log(count.value)
})
return {
count,
stop,
increase: () => {
count.value++
}
}
}
}).mount('#app')
</script>
</body>
TODO list案例
// App.vue
<template>
<section id="app" class="todoapp">
<header class="header">
<h1>todos</h1>
<input
class="new-todo"
placeholder="What needs to be done?"
autocomplete="off"
autofocus
v-model="input"
@keyup.enter="addTodo"
>
</header>
<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">
<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">
<label @dblclick="editTodo(todo)">{{ todo.text }}</label>
<button class="destroy" @click="remove(todo)"></button>
</div>
<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>
<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>
</section>
<footer class="info">
<p>Double-click to edit a todo</p>
<!-- Remove the below line ↓ -->
<p>Template by <a href="http://sindresorhus.com">Sindre Sorhus</a></p>
<!-- Change this out with your name and url ↓ -->
<p>Created by <a href="https://www.lagou.com">教瘦</a></p>
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
</footer>
</template>
<script>
import './assets/index.css'
import useLocalStorage from './utils/useLocalStorage'
import { ref, computed, onMounted, onUnmounted, watchEffect } from 'vue'
const storage = useLocalStorage()
// 1. 添加待办事项
const useAdd = todos => {
const input = ref('')
const addTodo = () => {
const text = input.value && input.value.trim()
if (text.length === 0) return
todos.value.unshift({
text,
completed: false
})
input.value = ''
}
return {
input,
addTodo
}
}
// 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
}
}
// 3. 编辑待办项
const useEdit = remove => {
let beforeEditingText = ''
const editingTodo = ref(null)
const editTodo = todo => {
beforeEditingText = todo.text
editingTodo.value = todo
}
const doneEdit = todo => {
if (!editingTodo.value) return
todo.text = todo.text.trim()
todo.text || remove(todo)
editingTodo.value = null
}
const cancelEdit = todo => {
editingTodo.value = null
todo.text = beforeEditingText
}
return {
editingTodo,
editTodo,
doneEdit,
cancelEdit
}
}
// 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)
}
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 = ''
}
}
onMounted(() => {
window.addEventListener('hashchange', onHashChange)
onHashChange()
})
onUnmounted(() => {
window.removeEventListener('hashchange', onHashChange)
})
return {
allDone,
count,
filteredTodos,
remainingCount
}
}
// 5. 存储待办事项
const useStorage = () => {
const KEY = 'TODOKEYS'
const todos = ref(storage.getItem(KEY) || [])
watchEffect(() => {
storage.setItem(KEY, todos.value)
})
return todos
}
export default {
name: 'App',
setup () {
const todos = useStorage()
const { remove, removeCompleted } = useRemove(todos)
return {
todos,
remove,
removeCompleted,
...useAdd(todos),
...useEdit(remove),
...useFilter(todos)
}
},
directives: {
editingFocus: (el, binding) => {
binding.value && el.focus()
}
}
}
</script>
<style>
</style>
需要注意的是这里的key需要是todo,如果是todo.text,那么一打字,这个li就重新创建,就会失去焦点,另外这里用了editingFocus的directive,双击todo以后自动focus这个input
<li
v-for="todo in filteredTodos"
:key="todo"
:class="{ editing: todo === editingTodo, completed: todo.completed }"
>
用hashchange事件来实现filter, all active completed
用localstorage存储todo
// utils/useLocalStorage.js
function parse (str) {
let value
try {
value = JSON.parse(str)
} catch {
value = null
}
return value
}
function stringify (obj) {
let 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
}
}