美文网首页
vue(一):vue 知识点

vue(一):vue 知识点

作者: 林ze宏 | 来源:发表于2019-07-07 21:03 被阅读0次

    目录

    • 1 Vue 实例
    • 2 Vue 的生命周期方法
    • 3 Vue 的数据绑定
    • 4 computed 和 watch 使用场景和方法
    • 5 Vue 的原生指令
    • 6 Vue 的组件之组件的定义
    • 7 Vue 的组件之组件的继承 extends
    • 8 Vue 的组件之自定义双向绑定
    • 9 Vue 的组件之高级属性
      9.1 插槽 slot
      9.2 跨级组件数据传递 provide 和 inject
    • 10 Vue 的组件之 render function
    • 11 vue-loader 相关配置

    1 Vue 实例

    官网:https://cn.vuejs.org/v2/api/index.html#vm-listeners

    实例:也就是通过 new 创建出来的对象。也就是在各个组件中的 this 对象。

    1:实例相关属性:

    • $data
    • $props
    • $el
    • $options
    • $parent
    • $root
    • $children
    • $refs
    • $slots
    • $scopedSlots
    • $isServer
    • $attrs
    • $listeners
    const app = new Vue({
      // el: '#root',
      template: '<div ref="div">{{text}} {{obj.a}}</div>',
      data: {
        text: 0,
        obj: {}
      }
    })
    
    // app 即为 vue 的实例
    app.$mount('#app')
    
    
    组件:
    <template>
      <div class="tab-container"></div>
    </template>
    
    <script>
    export default {
      props: {
        todos: {
          type: Array,
          required: true
        },
        state: {
          type: String,
          required: true
        }
      },
      inheritAttrs: true,
      mounted() {
        const vm = this; // !!!this 即为 vue 实例
        console.log(vm.$attrs);
        console.log(vm.$listeners);
      }
    };
    </script>
    
    
    

    例子

    Todos.vue 父组件:
    
    <template>
      <div class="main-content" ref="todos">
        <input
          class="ipt-text"
          autofocus="autofocus"
          @keyup.enter="addItem"
          v-model="iptText"
           ref="todos-input"
        />
        <item
          v-for="todo in filteredTodos"
          :key="todo.id"
          :todo="todo"
          @checkCompleted="checkCompleted"
          @removeItem="removeItem"
        />
        <tab
          :todos="todos"
          @clearNoActive111="clearNoActive"
          @filterItem="filterItem"
          :state="state"
          v-bind="$attrs"
        />
    
        <hr/>
        <blog-post>
          <template v-slot:header>
            <h1>About Me</h1>
          </template>
    
          <p>Here's some page content, which will be included in vm.$slots.default, because it's not inside a named slot.</p>
    
          <template v-slot:footer>
            <p>Copyright 2016 Evan You</p>
          </template>
    
          <p>If I have some content down here, it will also be included in vm.$slots.default.</p>.
        </blog-post>
      </div>
    </template>
    
    <script>
    import Vue from 'vue';
    import Item from './Item.vue';
    import Tab from './Tab.vue';
    
    Vue.component('blog-post', {
      render: function(createElement) {
        var header = this.$slots.header;
        var body = this.$slots.default;
        var footer = this.$slots.footer;
        return createElement('div', [
          createElement('header', header),
          createElement('main', body),
          createElement('footer', footer)
        ]);
      },
      mounted() {
        const vm = this;
        console.log(vm.$slots);
        console.log(vm.$scopedSlots);
      }
    });
    
    export default {
      name: 'main',
      components: { Item, Tab },
      data() {
        return {
          iptText: '',
          index: 0,
          state: 'all',
          todos: [],
          cacheTodos: []
        };
      },
      methods: {
        addItem(e) {
          this.todos.unshift({
            id: this.index++,
            name: e.target.value,
            completed: false
          });
          this.iptText = '';
        }
      },
      mounted() {
        const vm = this;
        console.log(vm.$data);
        console.log(vm.$props);
        console.log(vm.$el);
        console.log(vm.$options);
        console.log(vm.$parent);
        console.log(vm.$root);
        console.log(vm.$children);
        console.log(vm.$refs);
        console.log(vm.$isServer);
      }
    };
    </script>
    
    <style scoped>
    </style>
    
    
    
    Tab.vue 子组件:
    
    <template>
      <div class="tab-container">
        <div class="tab-content">
          <span>{{isCheckTodoNum}} item check</span>
          <div class="btn-group">
            <span :class="{active: state === 'all', btn : true}" @click="$emit('filterItem', 'all')">all</span>
            <span :class="{active: state === 'active', btn : true}" @click="$emit('filterItem', 'active')">active</span>
            <span :class="{active: state === 'noActive', btn : true}" @click="$emit('filterItem', 'noActive')">noActive</span>
          </div>
          <button @click="$emit('clearNoActive')">clear noActive</button>
        </div>
      </div>
    
    </template>
    
    <script>
    export default {
      props: {
        todos: {
          type: Array,
          required: true
        },
        state: {
          type: String,
          required: true
        }
      },
      computed: {
        isCheckTodoNum: function() {
          return this.$parent.todos.filter(todo => todo.completed).length;
        }
      },
      inheritAttrs: true,
      mounted() {
        const vm = this;
        console.log(vm.$attrs);
        console.log(vm.$listeners);
      }
    };
    </script>
    
    <style scoped>
    </style>
    
    
    

    说明:

    • $attrs

    1:$attrs 即为父组件传递给子组件,而子组件没有使用过的 props 属性;

    2:如果父组件中没有使用过的属性,想向下深层传递,例如:向孙组件传递没有使用过的 props 属性,则子组件需要使用 v-bind="$attrs"

    3:$attrs 结合 inheritAttrs: true

    例如:

    父组件:
    <template>
      <div class="hello">
        <Todos
          :dd="'dd'"
          :ss="'ss'"
        />
      </div>
    </template>
    
    子组件:Todos.vue
    <template>
        <tab
          :todos="todos"
          @clearNoActive="clearNoActive"
          @filterItem="filterItem"
          :state="state"
          v-bind="$attrs" // !!!
        />
    </template>
    
    <script>
    import Tab from './Tab.vue';
    </script>
    
    
    孙组件:Tab .vue
    <script>
    export default {
      props: {
        todos: {
          type: Array,
          required: true
        },
        state: {
          type: String,
          required: true
        }
      },
      inheritAttrs: true,
      mounted() {
        const vm = this;
        console.log(vm.$attrs);
      }
    };
    </script>
    
    

    参考:https://www.jianshu.com/p/ce8ca875c337

    2:实例方法

    • $watch(监听事件)
    • $on(注册事件)
    • $emit (触发事件 $emit$on 触发的事件必须为同一个实例对象)
    • $forceUpdate(强制重新更新渲染)
    • $set(设置数据,Vue 会监听该属性)
    • $delete(删除数据,会自动删除 Vue 绑定监听该属性上的事件)
    • $nextTick(获取真实DOM,回调)
      mounted() {
        const unWatch = this.$watch('iptText', (newText, oldText) => {
          // 组件销毁的时候,要手动注销
          console.log(`${newText} : ${oldText}`);
        });
        setTimeout(() => {
          unWatch();
        }, 5000);
      }
    
    也可以通过 watch 方法。
    <template>
      <div class="main-content" ref="todos">
        <input
          class="ipt-text"
          autofocus="autofocus"
          @keyup.enter="addItem"
          v-model="iptText"
           ref="todos-input"
        />
      </div>
    </template>
    
    <script>
    
    export default {
      name: 'main',
      components: { Item, Tab },
      data() {
        return {
          iptText: ''
        }
      },
      watch: { // 主动注销
        iptText(newText, oldText) {
          console.log(`${newText} : ${oldText}`);
        }
      }
    };
    </script>
    
    
    
    import Vue from 'vue'
    
    const app = new Vue({
      // el: '#root',
      template: '<div ref="div">{{text}} {{obj.a}}</div>',
      data: {
        text: 0,
        obj: {}
      }
      // watch: { // 主动注销
      //   text (newText, oldText) {
      //     console.log(`${newText} : ${oldText}`)
      //   }
      // }
    })
    
    app.$mount('#root')
    
    let i = 0
    setInterval(() => {
      i++
      // app.text += 1
      // app.text += 1
    
      app.$set(app.obj, 'a', i)
      // app.obj.a = i
      // app.$forceUpdate()
      // app.$delete(app.obj, 'a')
    }, 1000)
    
    // $emit 和 $on 触发的事件必须为同一个实例对象
    
    app.$on('test', (a, b) => {
      console.log(`test emited ${a} ${b}`)
    })
    
    // app.$once('test', (a, b) => {
    //   console.log(`test emited ${a} ${b}`)
    // })
    
    setInterval(() => {
      app.$emit('test', 1, 2)
    }, 1000)
    
    
    // app.$forceUpdate() // 强制组件重新渲染
    
    // $nextTick
    
    
    

    2 Vue 的生命周期方法

    生命周期
    import Vue from 'vue'
    
    const app = new Vue({
      // el: '#root',
      // template: '<div>{{text}}</div>',
      data: {
        text: 0
      },
      beforeCreate() {
        console.log(this.$el, 'beforeCreate')
      },
      created() {
        console.log(this.$el, 'created')
      },
      beforeMount() { // 服务端渲染不会触发,因为服务端渲染,根本就跟 DOM 挂载无关
        console.log(this.$el, 'beforeMount')
      },
      mounted() { // 服务端渲染不会触发,因为服务端渲染,根本就跟 DOM 挂载无关
        console.log(this.$el, 'mounted')
      },
      beforeUpdate() {
        console.log(this, 'beforeUpdate')
      },
      updated() {
        console.log(this, 'updated')
      },
      activated() { // 在组件章节讲解
        console.log(this, 'activated')
      },
      deactivated() { // 在组件章节讲解
        console.log(this, 'deactivated')
      },
      beforeDestroy() {
        console.log(this, 'beforeDestroy')
      },
      destroyed() {
        console.log(this, 'destroyed')
      },
      render(h) {
        throw new TypeError('render error')
        // console.log('render function invoked')
        // return h('div', {}, this.text)
      },
      renderError(h, err) { // 上线打包不能被调用
        return h('div', {}, err.stack)
      },
      errorCaptured() {
        // 如果在根组件定义,所有子组件报错都可以捕获到,除非子组件取消冒泡事件。
        // 会向上冒泡,并且正式环境可以使用
      }
    })
    
    app.$mount('#root')
    // setInterval(() => {
    //   app.text = app.text += 1
    // }, 1000)
    
    setTimeout(() => {
      app.$destroy() // 自动销毁组件
    }, 1000)
    
    

    3 Vue 的数据绑定

    :class、:style、v-html、@click

    参考 list-todo 例子:https://www.jianshu.com/writer#/notebooks/37329580/notes/48268401

    import Vue from 'vue'
    
    new Vue({
      el: '#root',
      // template: `
      //   <div :id="aaa" @click="handleClick">
      //     <p v-html="html"></p>
      //   </div>
      // `,
      template: `
        <div
          :class="[{ active: isActive }]"
          :style="[styles, styles2]"
        >
          <p>{{getJoinedArr(arr)}}</p>
        </div>
      `,
      data: {
        isActive: false,
        arr: [1, 2, 3],
        html: '<span>123</span>',
        aaa: 'main',
        styles: {
          color: 'red',
          appearance: 'none'
        },
        styles2: {
          color: 'black'
        }
      },
      methods: {
        handleClick () {
          alert('clicked') // eslint-disable-line
        },
        getJoinedArr (arr) {
          return arr.join(' ')
        }
      }
    })
    
    
    

    4 computed 和 watch 使用场景和方法

    参考:https://cn.vuejs.org/v2/guide/computed.html

    import Vue from 'vue'
    
    new Vue({
      el: '#root',
      template: `
        <div>
          <p>Name: {{name}}</p>
          <p>Name: {{getName()}}</p>
          <p>Number: {{number}}</p>
          <p>FullName: {{fullName}}</p>
          <p><input type="text" v-model="number"></p>
          <p>FirstName: <input type="text" v-model="firstName"></p>
          <p>LastName: <input type="text" v-model="lastName"></p>
          <p>Name: <input type="text" v-model="name"></p>
          <p>Obj.a: <input type="text" v-model="obj.a"></p>
        </div>
      `,
      data: {
        firstName: 'Jokcy',
        lastName: 'Lou',
        number: 0,
        fullName: '',
        obj: {
          a: 0
        }
      },
      computed: { // 属性需要通过计算再显示,一般 computed 不要设置值,computed 就是通过计算重新组合需要显示的值。
        // name() {
        //   console.log('new name')
        //   return `${this.firstName} ${this.lastName}`
        // },
        name: {
          get() {
            console.log('new name')
            return `${this.firstName} ${this.lastName}`
          },
          set(name) {
            const names = name.split(' ')
            this.firstName = names[0]
            this.lastName = names[1]
          }
        }
      },
      watch: {
        firstName: function (val) {
          this.fullName = val + ' ' + this.lastName
        },
        lastName: function (val) {
          this.fullName = this.firstName + ' ' + val
        }
      },
      // watch: { // 主要场景:监听某个属性
      //   'obj.a': {
      //     handler() { // 实际执行就是 handler 方法
      //       console.log('obj.a changed')
      //       this.obj.a += 1
      //     },
      //     immediate: true // 声明该属性之后,立即执行,而不是等到属性变化后才执行
      //     // deep: true // 如果直接监听 obj,使用 deep: true,则对象的所有属性都会被监听,性能开销比较大;可以直接监听对应的属性 'obj.a'
      //   }
      // },
      methods: {
        getName() {
          console.log('getName invoked')
          return `${this.firstName} ${this.lastName}`
        }
      }
    })
    
    
    

    说明:

    • 1:computed:属性需要通过计算再显示,一般 computed 不要设置值,computed 就是通过计算重新组合需要显示的值。

    • 2:watch:主要场景是监听某个属性,当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。

    watch 在首次加载时并不会马上触发,如果希望首次触发,可以配置:

    immediate: true

    watch 监听对象时,如果对象的某个属性发生改变是,是不会触发监听事件的,因为对象的地址并没有改变,监听的是对象的地址,但是,可以配置:

    deep: true

    如果直接监听 obj,使用 deep: true,则对象的所有属性都会被监听,性能开销比较大;可以直接监听对应的属性 'obj.a'

    <p>Reversed message: "{{ reversedMessage() }}"</p>

    var vm = new Vue({
      el: '#example',
      data: {
        message: 'Hello'
      },
      computed: {
        // 计算属性的 getter
        reversedMessage: function () {
          // `this` 指向 vm 实例
          return this.message.split('').reverse().join('')
        }
      }
    })
    
    
    // 在组件中
    methods: {
      reversedMessage: function () {
        return this.message.split('').reverse().join('')
      }
    }
    

    我们可以将同一函数定义为一个方法而不是一个计算属性。两种方式的最终结果确实是完全相同的。然而,不同的是计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。这就意味着只要 message 还没有发生改变,多次访问 reversedMessage 计算属性会立即返回之前的计算结果,而不必再次执行函数。

    相比之下,每当触发重新渲染时,调用方法将总会再次执行函数。

    我们为什么需要缓存?假设我们有一个性能开销比较大的计算属性 A,它需要遍历一个巨大的数组并做大量的计算。然后我们可能有其他的计算属性依赖于 A 。如果没有缓存,我们将不可避免的多次执行 A 的 getter!如果你不希望有缓存,请用方法来替代。

    Vue 提供了一种更通用的方式来观察和响应 Vue 实例上的数据变动:侦听属性。当你有一些数据需要随着其它数据变动而变动时,你很容易滥用 watch——特别是如果你之前使用过 AngularJS。然而,通常更好的做法是使用计算属性而不是命令式的 watch 回调。

    <div id="demo">{{ fullName }}</div>
    
    var vm = new Vue({
      el: '#demo',
      data: {
        firstName: 'Foo',
        lastName: 'Bar',
        fullName: 'Foo Bar'
      },
      watch: {
        firstName: function (val) {
          this.fullName = val + ' ' + this.lastName
        },
        lastName: function (val) {
          this.fullName = this.firstName + ' ' + val
        }
      }
    })
    

    上面代码是命令式且重复的。将它与计算属性的版本进行比较:

    var vm = new Vue({
      el: '#demo',
      data: {
        firstName: 'Foo',
        lastName: 'Bar'
      },
      computed: {
        fullName: function () {
          return this.firstName + ' ' + this.lastName
        }
      }
    })
    

    好得多了,不是吗?

    虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么 Vue 通过 watch 选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。

    5 Vue 的原生指令

    • v-pre
    • v-once
    • v-html
    • v-if
    • v-else-if
    • v-else
    • v-model
    • v-model.number(输入为number)
    • v-model.trim(自动去除首尾空格)
    • v-model.lazy(input 事件变为 change,懒加载)
    • v-for

    data 数组绑定复选框、以及单选框等。

    import Vue from 'vue'
    
    new Vue({
      el: '#root',
      template: `
        <div>
          <div v-pre>Text: {{text}}</div>
          <div v-once>Text: {{text}}</div>
          <div>Text: {{text}}</div>
          <div v-if="text === 0">Else Text: {{text}}</div>
          <div v-else>else content</div>
          <div v-html="html"></div>
          <input text="text" v-model="text">
          <input text="text" v-model.number="text">
          <input text="text" v-model.trim="text">
          <input text="text" v-model.lazy="text">
          <input type="checkbox" v-model="active">
          <div>
            <input type="checkbox" :value="1" v-model="arr">
            <input type="checkbox" :value="2" v-model="arr">
            <input type="checkbox" :value="3" v-model="arr">
          </div>
          <div>
            <input type="radio" value="one" v-model="picked">
            <input type="radio" value="two" v-model="picked">
          </div>
          <ul>
            <li v-for="(item, index) in arr" :key="item">{{item}}:{{index}}</li>
          </ul>
          <ul>
            <li v-for="(val, key, index) in obj">{{val}}:{{key}}:{{index}}</li>
          </ul>
        </div>
      `,
      data: {
        arr: [2, 3],
        obj: {
          a: '123',
          b: '456',
          c: '789'
        },
        picked: '',
        text: 0,
        active: false,
        html: '<span>this is html</span>'
      }
    })
    
    
    

    6 Vue 的组件之组件的定义

    • 方式一:全局注册
    Vue.component('CompOne', compoent) 
    
    规范:定义大写驼峰 CompOne,使用的时候,小写横杆 comp-one
    
    
    • 方式二:组件内部
    components: {
      CompOne: compoent
    },
    
    

    例子:

    import Vue from 'vue'
    
    const compoent = {
      props: {
        // active: Boolean,
        active: {
          // type: Boolean,
          // required: true,
          validator(value) { // !!!自定义校验
            return typeof value === 'boolean'
          }
          // default() { // !!!默认值必须和 data 一样通过函数返回值
          //   return false
          // }
        },
        propOne: String
      },
      template: `
        <div>
          <input type="text" v-model="text">
          <span @click="handleChange">{{propOne}}</span>
          <span v-show="active">see me if active</span>
        </div>
      `,
      data() { // 必须是函数 return
        return {
          text: 0
        }
      },
      methods: {
        handleChange() {
          this.$emit('change')
        }
      }
    }
    
    // Vue.component('CompOne', compoent) // 方式一,全局;规范:定义大写驼峰 CompOne,使用的时候,小写横杆comp-one
    /* eslint-disable no-new */
    
    new Vue({
      components: { // 方式二
        CompOne: compoent
      },
      data: {
        prop1: 'text1'
      },
      methods: {
        handleChange() {
          this.prop1 += 1
        }
      },
      mounted() {
        console.log(this.$refs.comp1)
      },
      el: '#app',
      template: `
        <div>
          <comp-one ref="comp1" :active="true" :prop-one="prop1" @change="handleChange"></comp-one>
          <comp-one :active="true" propOne="text2"></comp-one>
        </div>
      `
    })
    
    

    ** 说明:组件 props 属性定义和校验**

      props: {
        // active: Boolean,
        active: {
          // type: Boolean,
          // required: true,
          validator(value) { // !!!自定义校验
            return typeof value === 'boolean'
          }
          // default() { // !!!默认值必须和 data 一样通过函数返回值
          //   return false
          // }
        },
        propOne: String
      },
    
    

    7 Vue 的组件之组件的继承 extends

    extends: compoent,

    const CompVue = Vue.extend(compoent)

    组件继承之后,数据会覆盖,但是,组件各自的生命周期不会覆盖;如果要获取继承组件的 props,不能通过 props 获取,应该通过 propsData 属性。

    例子:

    import Vue from 'vue'
    
    const compoent = {
      props: {
        active: Boolean,
        propOne: String
      },
      template: `
        <div>
          <input type="text" v-model="text">
          <span @click="handleChange">{{propOne}}</span>
          <span v-show="active">see me if active</span>
        </div>
      `,
      data() {
        return {
          text: 0
        }
      },
      mounted() {
        console.log('comp mounted')
      },
      methods: {
        handleChange() {
          this.$emit('change')
        }
      }
    }
    
    const componet2 = {
      extends: compoent,
      data() {
        return {
          text: 1
        }
      },
      mounted() {
        // this.$parent.text = 12345; // 可以改变父组件的数据,但是不要修改,会混乱!!!
        console.log(this.$parent.$options.name) // Root
      }
    }
    
    // const CompVue = Vue.extend(compoent)
    
    // new CompVue({
    //   el: '#root',
    //   propsData: { // 通过 props 拿不到
    //     propOne: 'xxx'
    //   },
    //   data: { // 合并属性会覆盖
    //     text: '123'
    //   },
    //   mounted () { // 组件生命周期不会覆盖
    //     console.log('instance mounted')
    //   }
    // })
    
    const parent = new Vue({
      name: 'parent111'
    })
    
    /* eslint-disable no-new */
    new Vue({
      parent: parent,
      name: 'Root',
      el: '#app',
      mounted() {
        console.log(this.$parent.$options.name) // parent111
      },
      components: {
        Comp: componet2
      },
      data: {
        text: 23333
      },
      template: `
        <div>
          <span>{{text}}</span>
          <comp></comp>
        </div>
      `
    })
    
    
    

    8 Vue 的组件之自定义双向绑定

    • 定义组件,给 input 输入框,绑定 @input 事件 和 :value 值
    const component = {
      template: `
        <div>
          <input type="text" @input="handleInput" :value="value">
        </div>
      `,
      methods: {
        handleInput(e) {
          this.$emit('input', e.target.value)
        }
      }
    }
    
    

    使用组件:

    import Vue from 'vue'
    
    /* eslint-disable no-new */
    new Vue({
      components: {
        CompOne: component
      },
      el: '#app',
      data() {
        return {
          value: '123'
        }
      },
      template: `
        <div>
          <comp-one :value="value" @input="value = arguments[0]">默认 value + input 事件</comp-one>
        </div>
      `
    })
    
    
    • 等同于 v-model:( v-model 其实就是 value 值 + input 事件的绑定)
    import Vue from 'vue'
    
    /* eslint-disable no-new */
    new Vue({
      components: {
        CompOne: component
      },
      el: '#app',
      data() {
        return {
          value: '123'
        }
      },
      template: `
        <div>
          <comp-one v-model="value"></comp-one>
        </div>
      `
    })
    
    • input 事件改为 change:
    const component = {
      model: {
        event: 'change' // 默认为 input
      },
      template: `
        <div>
          <input type="text" @change="handleInput" :value="value">
        </div>
      `,
      methods: {
        handleInput(e) {
          this.$emit('change', e.target.value)
        }
      }
    }
    
    <comp-one :value="value" @change="value = arguments[0]"></comp-one>
    
    • 更改绑定的 value 属性为自定义属性,需要配置 model、props
    const component = {
      model: {
        prop: 'value1',
        event: 'change' // 默认为 input
      },
      props: ['value1'],
      // props: ['value'],  // 默认绑定为 value
      template: `
        <div>
          <input type="text" @input="handleInput" :value="value1">
        </div>
      `,
      methods: {
        handleInput(e) {
          this.$emit('change', e.target.value)
          // this.$emit('input', e.target.value)
        }
      }
    }
    
    
    <comp-one :value1="value" @change="value = arguments[0]"></comp-one>
    

    例子:

    import Vue from 'vue'
    
    const component = {
      model: {
        prop: 'value1',
        event: 'change' // 默认为 input
      },
      props: ['value1'],
      // props: ['value'],  // 默认绑定为 value
      template: `
        <div>
          <input type="text" @input="handleInput" :value="value1">
        </div>
      `,
      methods: {
        handleInput(e) {
          this.$emit('change', e.target.value)
          // this.$emit('input', e.target.value)
        }
      }
    }
    /* eslint-disable no-new */
    new Vue({
      components: {
        CompOne: component
      },
      el: '#app',
      data() {
        return {
          value: '123'
        }
      },
      template: `
        <div>
          <comp-one :value="value" @input="value = arguments[0]">默认 value + input 事件</comp-one>
          <comp-one :value1="value" @change="value = arguments[0]"></comp-one>
          <comp-one v-model="value"></comp-one>
        </div>
      `
    })
    
     v-model 其实就是 value 值 + input 事件的绑定。
    
    
    

    9 Vue 的组件之高级属性

    9.1 插槽 slot

    • 通过 name 指定对应的 slot
    • 在组件配置 slot-scope 获取插槽 slot 属性
    /* eslint-disable no-new */
    
    import Vue from 'vue'
    
    const component = {
      name: 'comp',
      // template: `
      //   <div :style="style">
      //     <div class="header">
      //       <slot name="header"></slot>
      //     </div>
      //     <div clas s="body">
      //       <slot name="body"></slot>
      //     </div>
      //   </div>
      // `,
      template: `
        <div :style="style">
          <slot></slot>
    
          <slot name="header"></slot>
          <slot name="footer"></slot>
    
          <slot :value="value" aaa="111"></slot>
        </div>
      `,
      data() {
        return {
          style: {
            width: '200px',
            height: '200px',
            border: '1px solid #aaa'
          },
          value: 'component value'
        }
      }
    }
    
    new Vue({
      components: {
        CompOne: component
      },
      el: '#app',
      data() {
        return {
          value: '123'
        }
      },
      mounted() {
        console.log(this.$refs.comp.value, this.$refs.span) // 一般不要使用 ref,避免滥用
      },
      template: `
        <div>
          <comp-one>
            <span>默认</span>
          </comp-one>
    
    
          <comp-one>
            <span slot="header">name 指定 header 位置</span>
          </comp-one>
          <comp-one>
            <span slot="footer">name 指定 footer 位置</span>
          </comp-one>
    
          <comp-one>
            <span slot="footer">name 指定 footer 位置,变量: {{value}}</span>
          </comp-one>
    
    
          <comp-one ref="comp">
            <span slot-scope="props" ref="span">{{props.value}} {{props.aaa}} {{value}}</span>
          </comp-one>
          <input type="text" v-model="value" />
        </div>
      `
    })
    
    
    

    9.2 跨级组件数据传递 provide 和 inject

    这种方式,孙组件通过 provide 注入的 value 值并不会跟随父组件实时渲染。
    
    /* eslint-disable no-new */
    import Vue from 'vue'
    
    const ChildComponent = {
      template: '<div>child component: {{value}}</div>',
      inject: ['yeye', 'value'], // 孙子获取父辈实例,需要有上下级关系!!!
      mounted() {
        console.log(this.yeye, this.value)
      }
    }
    
    const component = {
      name: 'comp',
      components: {
        ChildComponent
      },
      template: `
        <div :style="style">
          <slot :value="value" aaa="111"></slot>
          <child-component />
        </div>
      `,
      data() {
        return {
          style: {
            width: '200px',
            height: '200px',
            border: '1px solid #aaa'
          },
          value: 'component value'
        }
      }
    }
    
    new Vue({
      components: {
        CompOne: component
      },
      provide() {
        return { // value 默认不会提供渲染
          yeye: this, // !!!
          value: this.value
        }
      },
      el: '#app',
      data() {
        return {
          value: '123'
        }
      },
      mounted() {
        console.log(this.$refs.comp.value, this.$refs.span) // 一般不要使用 ref,避免滥用
      },
      template: `
        <div>
          <comp-one ref="comp">
            <span slot-scope="props" ref="span">{{props.value}} {{props.aaa}} {{value}}</span>
          </comp-one>
          <input type="text" v-model="value" />
        </div>
      `
    })
    
    
    

    provide 和 inject 改进,数据跟随实时渲染

    /* eslint-disable no-new */
    import Vue from 'vue'
    
    const ChildComponent = {
      template: '<div>child component: {{data.value}}</div>',
      inject: ['yeye', 'data'], // 孙子获取父辈实例,需要有上下级关系!!!
      mounted() {
        console.log(this.yeye, this.data.value)
      }
    }
    
    const component = {
      name: 'comp',
      components: {
        ChildComponent
      },
      template: `
        <div :style="style">
          <slot :value="value" aaa="111"></slot>
          <child-component />
        </div>
      `,
      data() {
        return {
          style: {
            width: '200px',
            height: '200px',
            border: '1px solid #aaa'
          },
          value: 'component value'
        }
      }
    }
    
    new Vue({
      components: {
        CompOne: component
      },
      provide() {
        const data = {}
    
        Object.defineProperty(data, 'value', {
          get: () => this.value,
          enumerable: true
        })
    
        return {
          yeye: this, // !!!
          data
        }
      },
      el: '#app',
      data() {
        return {
          value: '123'
        }
      },
      mounted() {
        console.log(this.$refs.comp.value, this.$refs.span) // 一般不要使用 ref,避免滥用
      },
      template: `
        <div>
          <comp-one ref="comp">
            <span slot-scope="props" ref="span">{{props.value}} {{props.aaa}} {{value}}</span>
          </comp-one>
          <input type="text" v-model="value" />
        </div>
      `
    })
    
    
    

    10 Vue 的组件之 render function

    在组件中,template 模板最终会被 Vue 编译成 render function,render 方法接收一个 createElement 方法参数,也可以通过内部属性 this.$createElement;

    createElement 方法接收三个参数,第一个为对应的节点名称,第二个为节点的属性,第三个为数组,可以是节点的数据或者内容,也可以继续通过 createElement 方法创建子节点。

    import Vue from 'vue'
    
    const component = {
      props: ['props1'],
      name: 'comp',
      // template: `
      //   <div :style="style">
      //     <slot></slot>
      //   </div>
      // `,
      render(createElement) {
        return createElement('div', {
          style: this.style
          // on: {
          //   click: () => { this.$emit('click') }
          // }
        }, [
            this.$slots.header, // this.$slots.default
            this.props1
          ])
      },
      data() {
        return {
          style: {
            width: '200px',
            height: '200px',
            border: '1px solid #aaa'
          },
          value: 'component value'
        }
      }
    }
    
    new Vue({
      components: {
        CompOne: component
      },
      el: '#root',
      data() {
        return {
          value: '123'
        }
      },
      mounted() {
        console.log(this.$refs.comp.value, this.$refs.span)
      },
      methods: {
        handleClick() {
          console.log('clicked')
        }
      },
      // template: ` 编译成js函数render方法;
      //   <comp-one ref="comp">
      //     <span ref="span">{{value}}</span>
      //   </comp-one>
      // `,
      render(createElement) { // this.$createElement;template编辑成render,整个流程是通过vue-loader处理的
        return createElement(
          'comp-one',
          {
            ref: 'comp',
            props: {
              props1: this.value
            },
            // on: {
            //   click: this.handleClick
            // },
            nativeOn: { // 绑定在组件根节点原生DOM上,自动处理
              click: this.handleClick
            }
          },
          [
            createElement('span', {
              ref: 'span',
              slot: 'header',
              // domProps: {
              //   innerHTML: '<span>sssssss</span>'
              // },
              attrs: {
                id: 'test-id'
              }
            }, this.value)
          ]
        )
      }
    })
    
    
    

    11 vue-loader 相关配置

    配置参考:

    相关文章

      网友评论

          本文标题:vue(一):vue 知识点

          本文链接:https://www.haomeiwen.com/subject/sgbihctx.html