Vue核心梳理

 

一、组件的核心概念-属性props几种写法

我们的开发都是围绕着options来的

<template>
<div>
name: {{ name }}
<br />
type: {{ type }}
<br />
list: {{ list }}
<br />
isVisible: {{ isVisible }}
<br />
<button @click="handleClick">change type</button>
</div>
</template>

<script>
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");
}
}
};
</script>
// 用法
<Props
name="Hello Vue!" // 原生属性
:type="type"
:is-visible="false"
:on-change="handlePropChange"
title="属性Demo" // 原生属性
class="test1" // 原生属性
:class="['test2']"
:
// 原生属性
/>

二、组件的核心概念-事件

<template>
<div>
name: {{ name || "--" }}
<br />
<input :value="name" @change="handleChange" />
<br />
<br />
<div @click="handleDivClick">
<button @click="handleClick">重置成功</button>&nbsp;&nbsp;&nbsp;
<button @click.stop="handleClick">重置失败</button>
</div>
</div>
</template>

<script>
export default {
name: "EventDemo",
props: {
name: String
},
methods: {
handleChange(e) {
this.$emit("change", e.target.value);
},
handleDivClick() {
this.$emit("change", "");
},
handleClick(e) {
// 都会失败
//e.stopPropagation();
}
}
};
</script>

三、组件的核心概念-插槽

 <a-tab-pane key="slot" tab="插槽">
<h2>2.6 新语法</h2>
<SlotDemo>
<p>default slot</p>
<template v-slot:title>
<p>title slot1</p>
<p>title slot2</p>
</template>
<template v-slot:item="props">
<p>item slot-scope {{ props }}</p>
</template>
</SlotDemo>
<br />
<h2>老语法</h2>
<SlotDemo>
<p>default slot</p>
<p slot="title">title slot1</p>
<p slot="title">title slot2</p>
<p slot="item" slot-scope="props">item slot-scope {{ props }}</p>
</SlotDemo>
</a-tab-pane>
<script>
import Slot from "./Slot";
export default {
components: {
SlotDemo: Slot
},
data: () => {
return {
name: "",
type: "success",
bigPropsName: "Hello world!"
};
},
};
</script>
<!-- Slot.vue -->
<template>
<div>
<slot />
<slot name="title" />
<slot name="item" v-bind="{ value: 'vue' }" />
</div>
</template>

<script>
export default {
name: "SlotDemo"
};
</script>

大属性例子

<!--子组件 bigProps.vue-->

<template>
<div>
{{ name }}
<br />
<button @click="handleChange">change name</button>
<br />
<!-- {{ slotDefault }} -->
<VNodes :vnodes="slotDefault" />
<br />
<VNodes :vnodes="slotTitle" />
<br />
<VNodes :vnodes="slotScopeItem({ value: 'vue' })" />
</div>
</template>

<script>
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!");
}
}
};
</script>
<!--父组件调用-->
<a-tab-pane key="bigProps" tab="大属性">
<BigProps
:name="bigPropsName"
:on-change="handleBigPropChange"
:slot-default="getDefault()"
:slot-title="getTitle()"
:slot-scope-item="getItem"
/>
</a-tab-pane>

四、双向绑定和单项数据流并不冲突

五、如何触发组件的更新

六、合理应用计算属性和监听器

6.1 计算属性Computed

  • 减少模板中的计算逻辑
  • 数据缓存
  • 依赖固定的数据类型(响应式数据)
<template>
<div>
<p>Reversed message1: "{{ reversedMessage1 }}"</p>
<p>Reversed message2: "{{ reversedMessage2() }}"</p>
<p>{{ now }}</p>
<button @click="() => $forceUpdate()">forceUpdate</button>
<br />
<input v-model="message" />
</div>
</template>
<script>
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("");
}
}
};
</script>

6.2 监听watcher

  • 更加灵活通用
  • watcher可以执行任何逻辑,包括函数节流、ajax异步获取数据
<template>
<div>
{{ $data }}
<br />
<button @click="() => (a += 1)">a+1</button>
</div>
</template>
<script>
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("😄");
console.log("new: %s, old: %s", val, oldVal);
},
deep: true
},
h(val, oldVal) {
console.log("new: %s, old: %s", val, oldVal);
}
}
};
</script>

watcher中使用节流

<template>
<div>
{{ fullName }}

<div>firstName: <input v-model="firstName" /></div>
<div>lastName: <input v-model="lastName" /></div>
</div>
</template>
<script>
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);
}
}
};
</script>

6.3 computed vs watcher

  • computed 能做的,watcher 都可以做,反之不行
  • 能用computed的尽量使用computed

七、生命周期的应用场景和函数式组件

7.1 生命周期

<template>
<div>
{{ log("render") }}
{{ now }}
<button @click="start = !start">{{ start ? "停止" : "开始" }}</button>
</div>
</template>
<script>
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);
}
}
}
};
</script>

打印顺序 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
<template functional>
<div>
{{ props }}
</div>
</template>
// 使用
<template>
<div>
<a-tabs>
<a-tab-pane key="Functional" tab="函数式组件">
<Functional :name="name" />
<TempVar
:var1="`hello ${name}`"
:var2="destroyClock ? 'hello vue' : 'hello world'"
>
<template v-slot="{ var1, var2 }">
{{ var1 }}
{{ var2 }}
</template>
</TempVar>
</a-tab-pane>
</a-tabs>
</div>
</template>
<script>
import Functional from "./Functional";
import TempVar from "./TempVar";
export default {
components: {
Functional,
TempVar
},
data() {
return {
destroyClock: false,
name: "vue"
};
}
};
</script>

八、Vue指令

8.1 内置指令

<template>
<div>
<h2>v-text</h2>
<div v-text="'hello vue'">hello world</div>
<h2>v-html</h2>
<div v-html="'<span style=&quot;color: red&quot;>hello vue</span>'">
hello world
</div>
<h2>v-show</h2>
<div v-show="show">hello vue</div>
<button @click="show = !show">change show</button>
<h2>v-if v-esle-if v-else</h2>
<div v-if="number === 1">hello vue {{ number }}</div>
<div v-else-if="number === 2">hello world {{ number }}</div>
<div v-else>hello geektime {{ number }}</div>
<h2>v-for v-bind</h2>
<div v-for="num in [1, 2, 3]" v-bind:key="num">hello vue {{ num }}</div>
<h2>v-on</h2>
<button v-on:click="number = number + 1">number++</button>
<h2>v-model</h2>
<input v-model="message" />
<h2>v-pre</h2>
<div v-pre>{{ this will not be compiled }}</div>
<h2>v-once</h2>
<div v-once>
{{ number }}
</div>
</div>
</template>
<script>
export default {
data: function() {
this.log = window.console.log;
return {
show: true,
number: 1,
message: "hello"
};
}
};
</script>

8.2 自定义指令

<template>
<div>
<button @click="show = !show">
销毁
</button>
<button v-if="show" v-append-text="`hello ${number}`" @click="number++">
按钮
</button>
</div>
</template>
<script>
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
};
}
};
</script>

九、template和jsx

9.1 JSX VS template

Template

  • 学习成本低
  • 大量内置指令简化开发
  • 组件作用域css
  • 但灵活性低

JSX

  • 总体上很灵活

9.2 以下是jsx写法

// index.vue
<script>
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 [<p>default slot</p>];
},
getTitle() {
return [<p>title slot1</p>, <p>title slot2</p>];
},
getItem(props) {
return [<p>{`item slot-scope ${JSON.stringify(props)}`}</p>];
}
},
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 (
<div>
<a-tabs>
<a-tab-pane key="props" tab="属性">
<Props
name="Hello Vue!"
type={type}
isVisible={false}
{...{ props: { onChange: handlePropChange } }}
title="属性Demo"
class="test1"
class={["test1", "test2"]}
style={{ marginTop: "10px" }}
/>
</a-tab-pane>
<a-tab-pane key="event" tab="事件">
<Event name={name} onChange={handleEventChange} />
</a-tab-pane>
<a-tab-pane key="slot" tab="插槽">
<SlotDemo {...slotDemoProps}>
<p>default slot</p>
<p slot="title">title slot1</p>
<p slot="title">title slot2</p>
</SlotDemo>
</a-tab-pane>
<a-tab-pane key="bigProps" tab="大属性">
<BigProps
name={bigPropsName}
{...bigProps}
slotDefault={getDefault()}
slotTitle={getTitle()}
slotScopeItem={getItem}
/>
</a-tab-pane>
</a-tabs>
</div>
);
}
};
</script>
// bigProps
<script>
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 (
<div>
{name}
<br />
<button onClick={handleChange}>change name</button>
<br />
{slotDefault}
<br />
{slotTitle}
<br />
{slotScopeItem({ value: "vue" })}
</div>
);
}
};
</script>
// Events.vue
<script>
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 (
<div>
name: {name || "--"}
<br />
<input value={name} onChange={handleChange} />
<br />
<br />
<div onClick={handleDivClick}>
<button onClick={handleClick}>重置成功</button>&nbsp;&nbsp;&nbsp;
<button onClick={e => handleClick(e, true)}>重置失败</button>
</div>
</div>
);
}
};
</script>
// Props.vue
<script>
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 (
<div>
name: {name}
<br />
type: {type}
<br />
list: {list}
<br />
isVisible: {isVisible}
<br />
<button onClick={handleClick}>change type</button>
</div>
);
}
};
</script>
// Slot
<script>
export default {
name: "SlotDemo",
render() {
const { $scopedSlots } = this;
return (
<div>
{$scopedSlots.default()}
{$scopedSlots.title()}
{$scopedSlots.item({ value: "vue" })}
</div>
);
}
};
</script>

十、为什么需要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

<template>
<div id="app">
{{count}}
<br>
{{$store.getters.doubleCount}}
<button @click="$store.commit('increment')">count++</button>
<button @click="$store.dispatch('increment')">count++</button>
</div>
</template>

<script>

export default {
name: 'app',
computed: {
count() {
return this.$store.state.count
}
}
}
</script>

<style>

</style>

十一、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.vue
<template>
<div id="app">
<h2>router demo</h2>
<router-view></router-view>
</div>
</template>

<script>

export default {
name: 'app',
components: {
},
}
</script>

<style>
#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;
}
</style>
// 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 的 <router-view /> 中
{ path: '/user/:id',
component: RouterDemo,
name: '3',
props: true,
children: [
{
// 当 /user/:id/profile 匹配成功,
// RouterChildrenDemo 会被渲染在 RouterDemo 的 <router-view/> 中
path: 'profile',
component: RouterChildrenDemo,
name: '3-1'
},
{
// 当 /user/:id/posts 匹配成功
// RouterChildrenDemo 会被渲染在 RouterDemo 的 <router-view/> 中
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模式:需要后端配合,IE9不兼容,可以使用强制刷新处理

原理

支持一下
 
 
posted @ 2021-06-14 17:40  DaisyLinux  阅读(246)  评论(0编辑  收藏  举报