导航
导航
文章目录󰁋
  1. 一、组件的核心概念-属性props几种写法
  2. 二、组件的核心概念-事件
  3. 三、组件的核心概念-插槽
  4. 四、双向绑定和单项数据流并不冲突
  5. 五、如何触发组件的更新
  6. 六、合理应用计算属性和监听器
    1. 6.1 计算属性Computed
    2. 6.2 监听watcher
    3. 6.3 computed vs watcher
  7. 七、生命周期的应用场景和函数式组件
    1. 7.1 生命周期
    2. 7.2 函数式组件
  8. 八、Vue指令
    1. 8.1 内置指令
    2. 8.2 自定义指令
  9. 九、template和jsx
    1. 9.1 JSX VS template
    2. 9.2 以下是jsx写法
  10. 十、为什么需要vuex
  11. 十一、vuex核心概念和底层原理
    1. 11.1 核心概念
    2. 11.2 底层原理
  12. 十二、vuex最佳实践
    1. 12.1 核心概念
    2. 12.2 使用常量代替Mutation事件类型
    3. 12.3 命名空间
    4. 12.4 实践例子
  13. 十三、vue-router使用场景
    1. 13.1 解决的问题
    2. 13.2 使用方式
    3. 13.3 例子
  14. 十四、路由的类型及底层原理

关注作者公众号

和万千小伙伴一起学习

公众号:前端进价之旅

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']"
:style="{ marginTop: '20px' }"
style="margin-top: 10px" // 原生属性
/>

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

<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不兼容,可以使用强制刷新处理

原理

支持一下
扫一扫,支持poetries
  • 微信扫一扫
  • 支付宝扫一扫