Vue核心梳理
2016 年 5 月 4 日
我们的开发都是围绕着
export default {
data: function() {
this.log = window.console.log;
return {
show: true,
number: 1,
message: "hello"
};
}
};
options
来的
name: {{ name }}export default { name: "PropsDemo", // inheritAttrs: false, // 这种写法不利于后期维护 // props: ['name', 'type', 'list', 'isVisible'], props: { name: String, type: { validator: function(value) { // 这个值必须匹配下列字符串中的一个 return ["success", "warning", "danger"].includes(value); } }, list: { type: Array, // 对象或数组默认值必须从一个工厂函数获取 default: () => [] }, isVisible: { type: Boolean, default: false }, onChange: { type: Function, default: () => {} } }, methods: { handleClick() { // 不要这么做、不要这么做、不要这么做 // this.type = "warning"; // 可以,还可以更好 this.onChange(this.type === "success" ? "warning" : "success"); } } };
type: {{ type }}
list: {{ list }}
isVisible: {{ isVisible }}
// 用法
二、组件的核心概念-事件
name: {{ name || "--" }}export default { name: "EventDemo", props: { name: String }, methods: { handleChange(e) { this.$emit("change", e.target.value); }, handleDivClick() { this.$emit("change", ""); }, handleClick(e) { // 都会失败 //e.stopPropagation(); } } };
三、组件的核心概念-插槽
2.6 新语法
default slot title slot1 title slot2 item slot-scope {{ props }}
老语法
default slot title slot1 title slot2 item slot-scope {{ props }} import Slot from "./Slot"; export default { components: { SlotDemo: Slot }, data: () => { return { name: "", type: "success", bigPropsName: "Hello world!" }; }, };
export default { name: "SlotDemo" };
大属性例子
{{ name }}export default { name: "BigProps", components: { VNodes: { functional: true, render: (h, ctx) => ctx.props.vnodes } }, props: { name: String, onChange: { type: Function, default: () => {} }, slotDefault: Array, slotTitle: Array, slotScopeItem: { type: Function, default: () => {} } }, methods: { handleChange() { this.onChange("Hello vue!"); } } };
四、双向绑定和单项数据流并不冲突
五、如何触发组件的更新
六、合理应用计算属性和监听器
6.1 计算属性Computed
- 减少模板中的计算逻辑
- 数据缓存
- 依赖固定的数据类型(响应式数据)
export default { data() { return { message: "hello vue" }; }, computed: { // 计算属性的 getter reversedMessage1: function() { console.log("执行reversedMessage1"); return this.message .split("") .reverse() .join(""); }, now: function() { return Date.now(); } }, methods: { reversedMessage2: function() { console.log("执行reversedMessage2"); return this.message .split("") .reverse() .join(""); } } };Reversed message1: "{{ reversedMessage1 }}" Reversed message2: "{{ reversedMessage2() }}" {{ now }}
6.2 监听watcher
watcher
{{ $data }}export default { data: function() { return { a: 1, b: { c: 2, d: 3 }, e: { f: { g: 4 } }, h: [] }; }, watch: { a: function(val, oldVal) { this.b.c += 1; console.log("new: %s, old: %s", val, oldVal); }, "b.c": function(val, oldVal) { this.b.d += 1; console.log("new: %s, old: %s", val, oldVal); }, "b.d": function(val, oldVal) { this.e.f.g += 1; console.log("new: %s, old: %s", val, oldVal); }, e: { handler: function(val, oldVal) { this.h.push(":smile:"); console.log("new: %s, old: %s", val, oldVal); }, deep: true }, h(val, oldVal) { console.log("new: %s, old: %s", val, oldVal); } } };
watcher中使用节流
{{ fullName }}export default { data: function() { return { firstName: "Foo", lastName: "Bar", fullName: "Foo Bar" }; }, watch: { firstName: function(val) { clearTimeout(this.firstTimeout); this.firstTimeout = setTimeout(() => { this.fullName = val + " " + this.lastName; }, 500); }, lastName: function(val) { clearTimeout(this.lastTimeout); this.lastTimeout = setTimeout(() => { this.fullName = this.firstName + " " + val; }, 500); } } };firstName:lastName:
6.3 computed vs watcher
-
computed
能做的,watcher
都可以做,反之不行 - 能用computed的尽量使用
computed
七、生命周期的应用场景和函数式组件
7.1 生命周期
{{ log("render") }} {{ now }}import moment from "moment"; export default { data: function() { console.log("data"); this.moment = moment; this.log = window.console.log; return { now: moment(new Date()).format("YYYY-MM-DD HH:mm:ss"), start: false }; }, watch: { start() { this.startClock(); } }, beforeCreate() { console.log("beforeCreate"); }, created() { console.log("created"); }, beforeMount() { console.log("beforeMount"); }, mounted() { console.log("mounted"); this.startClock(); }, beforeUpdate() { console.log("beforeUpdate"); }, updated() { console.log("updated"); }, beforeDestroy() { console.log("beforeDestroy"); clearInterval(this.clockInterval); }, destroyed() { console.log("destroyed"); }, methods: { startClock() { clearInterval(this.clockInterval); if (this.start) { this.clockInterval = setInterval(() => { this.now = moment(new Date()).format("YYYY-MM-DD HH:mm:ss"); }, 1000); } } } };
打印顺序 beforeCreate - data - created - beforeMount - render - mounted
7.2 函数式组件
functional:true this
// TempVar.js export default { functional: true, render: (h, ctx) => { return ctx.scopedSlots.default && ctx.scopedSlots.default(ctx.props || {}); } };
// Functional.vue{{ props }}
// 使用{{ var1 }} {{ var2 }}import Functional from "./Functional"; import TempVar from "./TempVar"; export default { components: { Functional, TempVar }, data() { return { destroyClock: false, name: "vue" }; } };
八、Vue指令
8.1 内置指令
v-text
hello worldv-html
<div v-html="'hello vue'"> hello worldv-show
hello vuev-if v-esle-if v-else
hello vue {{ number }}hello world {{ number }}hello geektime {{ number }}v-for v-bind
hello vue {{ num }}v-on
v-model
v-pre
{{ this will not be compiled }}v-once
{{ number }}
8.2 自定义指令
export default { directives: { appendText: { bind() { console.log("bind"); }, inserted(el, binding) { el.appendChild(document.createTextNode(binding.value)); console.log("inserted", el, binding); }, update() { console.log("update"); }, componentUpdated(el, binding) { el.removeChild(el.childNodes[el.childNodes.length - 1]); el.appendChild(document.createTextNode(binding.value)); console.log("componentUpdated"); }, unbind() { console.log("unbind"); } } }, data() { return { number: 1, show: true }; } };
九、template和jsx
9.1 JSX VS template
Template
- 学习成本低
- 大量内置指令简化开发
- 组件作用域css
- 但灵活性低
JSX
- 总体上很灵活
9.2 以下是jsx写法
// index.vue import Props from "./Props"; import Event from "./Event"; import Slot from "./Slot"; import BigProps from "./BigProps"; export default { components: { Props, Event, SlotDemo: Slot, BigProps }, data: () => { return { name: "", type: "success", bigPropsName: "Hello world!" }; }, methods: { handlePropChange(val) { this.type = val; }, handleEventChange(val) { this.name = val; }, handleBigPropChange(val) { this.bigPropsName = val; }, getDefault() { return [default slot]; }, getTitle() { return [title slot1
,title slot2
]; }, getItem(props) { return [{`item slot-scope ${JSON.stringify(props)}`}
]; } }, render() { const { type, handlePropChange, name, handleEventChange, bigPropsName, getDefault, getTitle, getItem, handleBigPropChange } = this; const slotDemoProps = { scopedSlots: { item(props) { return `item slot-scope ${JSON.stringify(props)}`; } }, props: {} }; const bigProps = { props: { onChange: handleBigPropChange } }; return (); } };default slot title slot1 title slot2
// bigProps export default { name: "BigProps", components: { VNodes: { functional: true, render: (h, ctx) => ctx.props.vnodes } }, props: { name: String, onChange: { type: Function, default: () => {} }, slotDefault: Array, slotTitle: Array, slotScopeItem: { type: Function, default: () => {} } }, methods: { handleChange() { this.onChange("Hello vue!"); } }, render() { const { name, handleChange, slotDefault, slotTitle, slotScopeItem } = this; return ({name}); } };
{slotDefault}
{slotTitle}
{slotScopeItem({ value: "vue" })}
// Events.vue export default { name: "EventDemo", props: { name: String }, methods: { handleChange(e) { this.$emit("change", e.target.value); }, handleDivClick() { this.$emit("change", ""); }, handleClick(e, stop) { console.log("stop", stop); if (stop) { e.stopPropagation(); } } }, render() { const { name, handleChange, handleDivClick, handleClick } = this; return (name: {name || "--"}); } };
// Props.vue export default { name: "PropsDemo", // inheritAttrs: false, // props: ['name', 'type', 'list', 'isVisible'], props: { name: String, type: { validator: function(value) { // 这个值必须匹配下列字符串中的一个 return ["success", "warning", "danger"].includes(value); } }, list: { type: Array, // 对象或数组默认值必须从一个工厂函数获取 default: () => [] }, isVisible: { type: Boolean, default: false }, onChange: { type: Function, default: () => {} } }, methods: { handleClick() { // 不要这么做、不要这么做、不要这么做 //this.type = "warning"; // 可以,还可以更好 this.onChange(this.type === "success" ? "warning" : "success"); } }, render() { const { name, type, list, isVisible, handleClick } = this; return (name: {name}); } };
type: {type}
list: {list}
isVisible: {isVisible}
// Slot export default { name: "SlotDemo", render() { const { $scopedSlots } = this; return ({$scopedSlots.default()} {$scopedSlots.title()} {$scopedSlots.item({ value: "vue" })}); } };
十、为什么需要vuex
Vuex运行机制
基本例子
import Vue from 'vue' import Vuex from 'vuex' import App from './App.vue' Vue.use(Vuex) Vue.config.productionTip = false const store = new Vuex.Store({ state: { count: 0, }, mutations: { increment(state) { state.count++ } }, actions: { increment({commit}) { setTimeout(()=>{ // state.count++ // 不要对state进行更改操作,应该通过commit交给mutations去处理 commit('increment') }, 3000) } }, getters: { doubleCount(state) { return state.count * 2 } } }) new Vue({ store, render: h => h(App), }).$mount('#app')
// App.vue{{count}}export default { name: 'app', computed: { count() { return this.$store.state.count } } }
{{$store.getters.doubleCount}}
十一、vuex核心概念和底层原理
11.1 核心概念
11.2 底层原理
简化版本的vuex
import Vue from 'vue' const Store = function Store (options = {}) { const {state = {}, mutations={}} = options // 把state进行响应式和vue写法一样 this._vm = new Vue({ data: { $$state: state }, }) this._mutations = mutations } Store.prototype.commit = function(type, payload){ if(this._mutations[type]) { this._mutations[type](this.state, payload) } } Object.defineProperties(Store.prototype, { // 当我们取值 如 $store.getter.count的时候就会触发这里 state: { get: function(){ return this._vm._data.$$state } } }); export default {Store}
十二、vuex最佳实践
12.1 核心概念
12.2 使用常量代替Mutation事件类型
12.3 命名空间
对所有模块开启命名空间
12.4 实践例子
DEMO地址 https://github.com/poetries/vuex-demo
十三、vue-router使用场景
13.1 解决的问题
13.2 使用方式
13.3 例子
// main.js import Vue from 'vue' import VueRouter from 'vue-router' import App from './App.vue' import routes from './routes' Vue.config.productionTip = false Vue.use(VueRouter) const router = new VueRouter({ mode: 'history', routes, }) new Vue({ router, render: h => h(App), }).$mount('#app')
// App.vueexport default { name: 'app', components: { }, } #app { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; }router demo
// routes.js import RouterDemo from './components/RouterDemo' import RouterChildrenDemo from './components/RouterChildrenDemo' const routes = [ { path: '/foo', component: RouterDemo, name: '1' }, { path: '/bar', component: RouterDemo, name: '2' }, // 当 /user/:id 匹配成功, // RouterDemo 会被渲染在 App 的 中 { path: '/user/:id', component: RouterDemo, name: '3', props: true, children: [ { // 当 /user/:id/profile 匹配成功, // RouterChildrenDemo 会被渲染在 RouterDemo 的 中 path: 'profile', component: RouterChildrenDemo, name: '3-1' }, { // 当 /user/:id/posts 匹配成功 // RouterChildrenDemo 会被渲染在 RouterDemo 的 中 path: 'posts', component: RouterChildrenDemo } ] }, { path: '/a', redirect: '/bar' }, { path: '*', component: RouterDemo, name: '404' } ] export default routes
更多详情 https://github.com/poetries/vue-router-demo
十四、路由的类型及底层原理
路由的类型
Hash History
原理