Vue核心梳理

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

  
name: {{ name }}
type: {{ type }}
list: {{ list }}
isVisible: {{ isVisible }}
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"); } } };

// 用法

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

  
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

  • 减少模板中的计算逻辑
  • 数据缓存
  • 依赖固定的数据类型(响应式数据)
  

Reversed message1: "{{ reversedMessage1 }}" Reversed message2: "{{ reversedMessage2() }}" {{ now }}

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(""); } } };

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 }}
firstName:
lastName:
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); } } };

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 world

v-html

<div v-html="'hello vue'"> hello world

v-show

hello vue

v-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 }}
export default { data: function() { this.log = window.console.log; return { show: true, number: 1, message: "hello" }; } };

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}}
{{$store.getters.doubleCount}}
export default { name: 'app', computed: { count() { return this.$store.state.count } } }

十一、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

  

router demo

export 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; }

// 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

原理