东莞网站建设市场/做网站平台需要多少钱
文章目录
- Vue 3.0 和 2.x 的区别
- 源码组织方式
- packages 目录结构
- 不同构建版本
- Composition API 设计动机
- 设计动机
- Options API Demo:
- Composition API Demo:
- 对比:
- 性能提升
- 响应式系统升级
- 编译优化
- 优化打包体积
- Vite
- ESModule
- Vite as Vue-CLI
- Vite 特点
- Composition API
- Composition API
- 生命周期钩子函数
- reactive|toRefs|ref
- computed
- watch
- watchEffect
- todoList
- todoList - 功能演示
- todoList - demo
Vue 3.0 和 2.x 的区别
- 源码组织方式的变化
- Composition API
- 性能提升
- Vite
源码全部采用 TS 重写,组织方式也发生变化,使用 Monorepo
的方式来组织项目的结构,把独立的功能模块都提取到不同的包中。
虽然重写,但是 90% 都兼容 2.x 的API,根据社区反馈,增加了 Composition API,组合式API,旨在解决开发超大型项目遇到超大组件使用Options API 不好拆分重用的问题。
性能方面,大幅度提升,使用 Proxy(代理对象) 重写了响应式代码,并且对编译器做了优化,重写了虚拟DOM,让渲染和update的性能有了大幅度的提升。另外,官方介绍,服务端渲染的性能也提升了 2-3 倍。
Vite 工具的发布,使开发阶段不需要打包直接运行项目,提升开发效率。
源码组织方式
packages 目录结构
不同构建版本
Vue3.0中不再构建 UMD 模块化的方式,因为冗余太多。
构建版本:4类
CommonJS:
- cjs
- vue.cjs.js
- vue.cjs.prod.js
Global: 可以直接通过 script 标签直接导入
- global
- vue.global.js
- vue.global.prod.js
- vue.runtime.global.js
- vue.runtime.global.prod.js
Browser: ESModule ,可以直接通过 script type=module 的方式导入
- browser
- vue.esm-browser.js
- vue.esm-browser.prod.js
- vue.runtime.esm-browser.js
- vue.runtime.esm-browser.prod.js
Bundler: 需要配合打包工具来使用,ESM 方式
- bundler
- vue.esm-bundler.js
- vue.runtime.esm-bundler.js
Composition API 设计动机
- RFC(request for comments)
- https://github.com/vuejs/rfc
- Composition API RFC
- https://composition-api.vuejs.org
设计动机
-
Options API
- 包含一个描述组件选项(data.methods.props等)对象
- Options API 开发复杂组件,同一个功能逻辑的代码被拆分到不同选项
-
Composition API
- Vue.js 3.0 新增的一组 API
- 一组基于函数的 API
- 可以更灵活的组织组件的逻辑
Options API Demo:
Composition API Demo:
对比:
性能提升
- 响应式系统升级
- 编译优化
- 源码体积的优化
响应式系统升级
编译优化
优化打包体积
Vite
Vite 来自于 法语,意思是 快。
ESModule
Vite as Vue-CLI
Vite 特点
Composition API
注意:Composition API 仅仅是 Vue3中新增的 API,我们依然可以使用 Options API。
Composition API
生命周期钩子函数
reactive|toRefs|ref
ref demo:
<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 ++}}}const app = createApp({setup() {return {...useCount()}}})app.mount('#app')</script>
</body>
Composition API 生命周期 reactive toRefs ref demo:
<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() {let position = reactive({x: 0,y: 0})const update = (e) => {position.x = e.pageXposition.y = e.pageY}onMounted(() => {window.addEventListener('mousemove', update)})onUnmounted(() => {window.removeEventListener('mousemove', update)})return toRefs(position)}const app = createApp({setup() {// const position = useMousePosition()const { x, y } = useMousePosition()return { x, y }}})app.mount('#app')</script>
</body>
computed
<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 }]const app = createApp({setup() {const todos = reactive(data)const activeCount = computed(() => {return todos.filter((item) => !item.completed).length})return {activeCount,push:()=>{todos.push({text: '看电视',completed: false})}}}})app.mount('#app')</script>
</body>
watch
<body><div id="app"><p>请问一个 yes/no 的问题:<input v-model="question"></p><p>{{answer}}<img :src="img"></p></div><script type="module">// https://www.yesno.wtf/apiimport { createApp, ref, watch } from './node_modules/vue/dist/vue.esm-browser.js'const app = createApp({setup() {const question = ref('')const answer = ref('')const img = ref('')watch(question, async (newValue, oldValue)=>{console.log()const response = await fetch('https://www.yesno.wtf/api')const {answer: as, image: src} = await response.json()answer.value = asimg.value = src})return {question,answer,img}}})app.mount('#app')</script>
</body>
watchEffect
<body><div id="app"><button @click="increase">increase</button><button @click="stop">stop</button><br>{{count}}</div><script type="module">// https://www.yesno.wtf/apiimport { createApp, ref, watchEffect } from './node_modules/vue/dist/vue.esm-browser.js'const app = createApp({setup() {const count = ref(0)const stop = watchEffect(()=>{console.log(count.value)})return {count,stop,increase:()=>{count.value ++}}}})app.mount('#app')</script>
</body>
todoList
todoList - 功能演示
todoList - demo
vue 代码:
<template><section id="app" class="todoapp"><header class="header"><h1>todos</h1><inputclass="new-todo"placeholder="请输入代办项目"autocomplete="off"autofocusv-model="input"@keyup.enter="addTodo"/></header><section class="main" v-show="count"><input id="toggle-all" class="toggle-all" type="checkbox" v-model="allDone"/><label for="toggle-all">Mark all as complete</label><ul class="todo-list"><liv-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><inputclass="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">全部</a></li><li><a href="#/active">待完成</a></li><li><a href="#/completed">已完成</a></li></ul><button class="clear-completed" @click="removeCompleted" v-show="count > remainingCount">Clear completed</button></footer></section><footer class="info"><p>双击编辑单个项目</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 { computed, onMounted, onUnmounted, ref, watchEffect } from "vue";
const storage = useLocalStorage()
// 添加代办事项
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,};
};
// 删除代办事项
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 };
};
// 编辑代办事项
const useEdit = (remove) => {// 1、双击待办事项,展示文本框// 2、按回车或者编辑文本框失去焦点,修改数据// 3、按ESC取消编辑// 4、把编辑文本框清空,按回车,删除这一项// 5、显示编辑文本框的时候,获取焦点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,};
};
/*** 切换代办项完成状态* 1、点击 checkbox 改变所有代办项状态* 2、All/Active/Completed* 3、其他* 3-1、显示未完成代办项个数* 3-2、移除所有已完成项目* 3-3、如果没有代办项,隐藏 main 和 footer*/
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 {count,allDone,filteredTodos,remainingCount}
}// 存储待办事项
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 = ref([]);const todos = useStorage();const { remove, ...removes } = useRemove(todos);return {todos,remove,...removes,...useAdd(todos),...useEdit(remove),...useFilter(todos)};},directives: {editingFocus: (el, binding)=>{binding.value && el.focus()}}
};
</script>
<style scoped>
</style>
index.css
html,
body {margin: 0;padding: 0;
}button {margin: 0;padding: 0;border: 0;background: none;font-size: 100%;vertical-align: baseline;font-family: inherit;font-weight: inherit;color: inherit;-webkit-appearance: none;appearance: none;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;
}body {font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;line-height: 1.4em;background: #f5f5f5;color: #111111;min-width: 230px;max-width: 550px;margin: 0 auto;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;font-weight: 300;
}:focus {outline: 0;
}.hidden {display: none;
}.todoapp {background: #fff;margin: 130px 0 40px 0;position: relative;box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),0 25px 50px 0 rgba(0, 0, 0, 0.1);
}.todoapp input::-webkit-input-placeholder {font-style: italic;font-weight: 300;color: rgba(0, 0, 0, 0.4);
}.todoapp input::-moz-placeholder {font-style: italic;font-weight: 300;color: rgba(0, 0, 0, 0.4);
}.todoapp input::input-placeholder {font-style: italic;font-weight: 300;color: rgba(0, 0, 0, 0.4);
}.todoapp h1 {position: absolute;top: -140px;width: 100%;font-size: 80px;font-weight: 200;text-align: center;color: #b83f45;-webkit-text-rendering: optimizeLegibility;-moz-text-rendering: optimizeLegibility;text-rendering: optimizeLegibility;
}.new-todo,
.edit {position: relative;margin: 0;width: 100%;font-size: 24px;font-family: inherit;font-weight: inherit;line-height: 1.4em;color: inherit;padding: 6px;border: 1px solid #999;box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);box-sizing: border-box;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;
}.new-todo {padding: 16px 16px 16px 60px;border: none;background: rgba(0, 0, 0, 0.003);box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);
}.main {position: relative;z-index: 2;border-top: 1px solid #e6e6e6;
}.toggle-all {width: 1px;height: 1px;border: none; /* Mobile Safari */opacity: 0;position: absolute;right: 100%;bottom: 100%;
}.toggle-all + label {width: 60px;height: 34px;font-size: 0;position: absolute;top: -52px;left: -13px;-webkit-transform: rotate(90deg);transform: rotate(90deg);
}.toggle-all + label:before {content: '❯';font-size: 22px;color: #e6e6e6;padding: 10px 27px 10px 27px;
}.toggle-all:checked + label:before {color: #737373;
}.todo-list {margin: 0;padding: 0;list-style: none;
}.todo-list li {position: relative;font-size: 24px;border-bottom: 1px solid #ededed;
}.todo-list li:last-child {border-bottom: none;
}.todo-list li.editing {border-bottom: none;padding: 0;
}.todo-list li.editing .edit {display: block;width: calc(100% - 43px);padding: 12px 16px;margin: 0 0 0 43px;
}.todo-list li.editing .view {display: none;
}.todo-list li .toggle {text-align: center;width: 40px;/* auto, since non-WebKit browsers doesn't support input styling */height: auto;position: absolute;top: 0;bottom: 0;margin: auto 0;border: none; /* Mobile Safari */-webkit-appearance: none;appearance: none;
}.todo-list li .toggle {opacity: 0;
}.todo-list li .toggle + label {/*Firefox requires `#` to be escaped - https://bugzilla.mozilla.org/show_bug.cgi?id=922433IE and Edge requires *everything* to be escaped to render, so we do that instead of just the `#` - https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7157459/*/background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23ededed%22%20stroke-width%3D%223%22/%3E%3C/svg%3E');background-repeat: no-repeat;background-position: center left;
}.todo-list li .toggle:checked + label {background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23bddad5%22%20stroke-width%3D%223%22/%3E%3Cpath%20fill%3D%22%235dc2af%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22/%3E%3C/svg%3E');
}.todo-list li label {word-break: break-all;padding: 15px 15px 15px 60px;display: block;line-height: 1.2;transition: color 0.4s;font-weight: 400;color: #4d4d4d;
}.todo-list li.completed label {color: #cdcdcd;text-decoration: line-through;
}.todo-list li .destroy {display: none;position: absolute;top: 0;right: 10px;bottom: 0;width: 40px;height: 40px;margin: auto 0;font-size: 30px;color: #cc9a9a;margin-bottom: 11px;transition: color 0.2s ease-out;
}.todo-list li .destroy:hover {color: #af5b5e;
}.todo-list li .destroy:after {content: '×';
}.todo-list li:hover .destroy {display: block;
}.todo-list li .edit {display: none;
}.todo-list li.editing:last-child {margin-bottom: -1px;
}.footer {padding: 10px 15px;height: 20px;text-align: center;font-size: 15px;border-top: 1px solid #e6e6e6;
}.footer:before {content: '';position: absolute;right: 0;bottom: 0;left: 0;height: 50px;overflow: hidden;box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),0 8px 0 -3px #f6f6f6,0 9px 1px -3px rgba(0, 0, 0, 0.2),0 16px 0 -6px #f6f6f6,0 17px 2px -6px rgba(0, 0, 0, 0.2);
}.todo-count {float: left;text-align: left;
}.todo-count strong {font-weight: 300;
}.filters {margin: 0;padding: 0;list-style: none;position: absolute;right: 0;left: 0;
}.filters li {display: inline;
}.filters li a {color: inherit;margin: 3px;padding: 3px 7px;text-decoration: none;border: 1px solid transparent;border-radius: 3px;
}.filters li a:hover {border-color: rgba(175, 47, 47, 0.1);
}.filters li a.selected {border-color: rgba(175, 47, 47, 0.2);
}.clear-completed,
html .clear-completed:active {float: right;position: relative;line-height: 20px;text-decoration: none;cursor: pointer;
}.clear-completed:hover {text-decoration: underline;
}.info {margin: 65px auto 0;color: #4d4d4d;font-size: 11px;text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);text-align: center;
}.info p {line-height: 1;
}.info a {color: inherit;text-decoration: none;font-weight: 400;
}.info a:hover {text-decoration: underline;
}/*Hack to remove background from Mobile Safari.Can't use it globally since it destroys checkboxes in Firefox
*/
@media screen and (-webkit-min-device-pixel-ratio:0) {.toggle-all,.todo-list li .toggle {background: none;}.todo-list li .toggle {height: 40px;}
}@media (max-width: 430px) {.footer {height: 50px;}.filters {bottom: 10px;}
}
utils/useLocalStorage.js
function parse(str){let valuetry{value = JSON.parse(str)}catch{value = null}return value
}
function stringify(obj){let valuetry{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}
}
环境&依赖
node 18.16.0
vue 3.3.2
自定义指令一、
自定义指令二、