文章目录


课程资源:链接:https://pan.baidu.com/s/1OhjafwGM48nny7lPDpC-6Q 提取码:qv0o

1 Vue 语法初探

1-1 课前须知

  • 如何使用vue完成项目的开发

  • 深度理解特性背后的原理


在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

1-2 初学编写 HelloWorld 和 Counter

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>hello world</title>
	<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
    // Vue.createApp({ // 创建vue实例
    //     template: '<div>hello world</div>' // 在root元素里面用此模板展示内容
    // }).mount('#root'); // 在id为root的元素上使用vue

    /**
     * 1.找到root节点,往上面挂一个元素
     * 2.元素的内容就是template里面的内容
     * 3.用{{}}}:表示这是js表达式,是一个变量
     *      <div id="root">
     *          <div>{{content}}</div>
     *      </div>
     */
    Vue.createApp({
        data() { // 定义数据
            return {
                content: 1
            }
        },
	    // 当页面加载完成,就会执行mounted函数,自动执行函数
	    mounted() { // 自动执行
            // console.log('mounted')
		    setInterval(() => {
                this.content += 1
		    }, 1000)
	    },
        template: '<div>{{content}}</div>' // 渲染内容
    }).mount('#root'); // 渲染节点
</script>
</html>

此后的代码省略了html的head、body部分,只写js部分代码

1-3 编写字符串反转和内容隐藏小功能

	// 反转字符串
    // Vue.createApp({
    //     data() {
    //         return {
    //             content: 'hello world'
    //         }
    //     },
    //     methods: { // 定义函数
    //         handleBtnClick() {
    //             // 将content内容反转
    //             this.content = this.content.split('').reverse().join('')
    //         }
    //     },
    //     template: `
    //       <div>
    //       	{{ content }}
    //       <!-- 给button元素绑定指令v-on:click,v-on:绑定事件,click:绑定的是点击事件,
    //       		事件会执行一个函数,handleBtnClick表示执行的函数名
    //       -->
    //       	<button v-on:click="handleBtnClick">反转</button>
    //       </div>
    // 	`
    // }).mount('#root');

    // 显示/隐藏
    Vue.createApp({
        data() {
            return {show: true}
        },
        methods: {
            handleBtnClick() {
                // 显示/隐藏
                this.show = !this.show
            }
        },
        template: `
          <div>
          <!-- v-if:标签的存在与否,此处取决于show的值-->
          <span v-if="show">hello world</span>
          <button v-on:click="handleBtnClick">显示/隐藏</button>
          </div>
		`
    }).mount('#root');

1-4 编写TodoList 小功能,了解循环和双向绑定

	// 循环list数据
    // Vue.createApp({
    //     data() {
    //         return {
    //             list: ['hello', 'world', 'dell', 'lee']
    //         }
    //     },
    //     template: `
    //       <ul>
    //       	<!-- 循环list数据-->
    //       	<li v-for="(item, index) of list">{{item}} {{index}}</li>
    //       </ul>
    // 	`
    // }).mount('#root');

    // 增加
    Vue.createApp({
        data() {
            return {
                inputValue: '',
                list: []
            }
        },
        methods: {
            handleAddItem() {
                this.list.push(this.inputValue)
                this.inputValue = '' // 增加完之后,input框清空
            }
        },
        template: `
          <div>
          <!-- v-model:将数据与输入框进行双向绑定-->
          <input v-model="inputValue"/>
          <button v-on:click="handleAddItem">增加</button>
          <ul>
            <li v-for="item of list">{{ item }}</li>
          </ul>
          </div>
		`
    }).mount('#root');

1-5 组件概念初探,对 TodoList 进行组件代码拆分

const app = Vue.createApp({ // 创建一个vue实例app
        data() {
            return {
                inputValue: '',
                list: []
            }
        },
        methods: {
            handleAddItem() {
                this.list.push(this.inputValue)
                this.inputValue = ''
            }
        },
        template: `
          <div>
          <!-- v-model:将数据与输入框进行双向绑定-->
          <input v-model="inputValue"/>
          <button
              v-on:click="handleAddItem"
              v-bind:title="inputValue"
          >
            <!-- v-bind:将标签上的数据与某个属性绑定-->
            <!-- 在标签内部表示数据,直接用{{}}-->
            增加 <!--  {{ inputValue }}-->
          </button>
          <ul>
            <todo-item
                v-for="(item, index) of list"
                v-bind:content="item"
                v-bind:index="index"
            />
          </ul>
          </div>
		`
    })

    // 在app上注册组件:组件是页面的一部分
    app.component('todo-item', {
        props: ['content', 'index'], // 接收外部传递的内容
        template: '<li>{{index}} -- {{content}}</li>'
    })

    // 在app上挂载实例
    app.mount('#root')

2 Vue 基础语法

2-1 Vue 中应用和组件的基础概念

	// createApp:创建一个Vue应用,存储到app变量当中
    // 传入的参数:这个应用最外层的组件该如何展示
    // mvvm设计模式: m->model 数据,v->view 视图,vm->viewModel 视图数据连接层
    const app = Vue.createApp({
        // 以下内容是app的参数
        // model
        data() {
            return {
                message: 'hello world'
            }
        },
        // view
        template: "<div>{{message}}</div>"
    })
    // vm:Vue的根组件,viewModel
    const vm = app.mount('#root')
    // 可以通过vm操作数据
    // vm.$data.message = 'bye'

2-2 理解 Vue 中的生命周期函数

/**
     * 生命周期函数:在某一时刻会自动执行的函数  (钩子=生命周期函数)
     * 关键点:某一时刻
     *       自动执行:eg.mounted()
     */
    const app = Vue.createApp({
        data() {
            return {
                message: 'hello world'
            }
        },
        // 在实例生成之前自动执行的函数
        beforeCreate() {
            console.log('beforeCreate')
        },
        // 在实例生成之后自动执行的函数
        created() {
            console.log('created')
        },
        // 在组件内容被渲染到页面之前自动执行的函数
        beforeMount() {
            console.log(document.getElementById('root').innerHTML, 'beforeMount') //  beforeMount
        },
        // 在组件内容被渲染到页面之后自动执行的函数
        mounted() {
            console.log(document.getElementById('root').innerHTML, 'mounted') // <div>hello world</div> mounted
        },
        // 当数据发生变化时,会自动执行的函数
        beforeUpdate() {
            console.log(document.getElementById('root').innerHTML, 'beforeUpdate') // <div>hello world</div> beforeUpdate
        },
        // 当数据发生变化,页面重新渲染后,会自动执行的函数
        updated() {
            console.log(document.getElementById('root').innerHTML, 'updated') // <div>bye</div> beforeUpdate
        },
	    // 当vue应用场景失效时,会自动执行的函数
        beforeUnmount() {
            console.log(document.getElementById('root').innerHTML, 'beforeUnmount') // <div>hello world</div> beforeUnmount
        },
        // 当vue应用场景失效时,且 dom 完全销毁后,会自动执行的函数
        unmounted() {
            console.log(document.getElementById('root').innerHTML, 'unmounted') // unmounted
        },
	    // 如果没有template属性,内容写在了body下的div root下,生命周期函数也是如此
        template: "<div>{{message}}</div>" // 先将模板变成函数,再渲染
    })
    const vm = app.mount('#root')

2-3 常用模版语法讲解

    const app = Vue.createApp({
        data() {
            return {
                message: 'hello world',
                disabled: false,
                show: true,
                event: 'click' // 动态参数
            }
        },
        methods: {
            handleClick() {
                alert('click')
            },
            // pd(e) {
            //     e.preventDefault() // 阻止默认行为
            // }
        },
        template: `
          <!--
              {{message}}:花括号内部可写js表达式
              v-once:表达式只执行一次,可以提高渲染性能
          -->
          <div v-once>{{ message }}</div>
          <!--
                v-html="message":在div上通过html方式展示message变量
                v-bind:title="message":展示标签内容,并与数据message进行绑定
                v-bind:title等价于:title(简写)
         -->
          <div v-bind:title="message">hello world</div>
          <!-- 与disabled值进行绑定,设置输入框input的状态 -->
          <input v-bind:disabled="disabled"/>
          <!-- v-if="show":div是否展示有show决定 -->
          <div v-if="show">{{ message }}</div>
          <!--
              1.v-on:事件绑定,事件方法handleClick写在methods中
              2.v-on:等价于@(简写)
              3.:[event]:动态属性
              v-on:click="handleClick"
              @click="handleClick"
              @[event]="handleClick"
          -->
          <div @click="handleClick">{{ message }}</div>
          <!--
            	阻止默认行为:
          		1.在函数内部完成功能:e.preventDefault()
				2.使用修饰符:.prevent(常用)
          -->
		<!-- <form action="https://www.baidu.com" @click="pd">-->
          <form action="https://www.baidu.com" @click.prevent="handleClick">
           <button>提交</button>
          </form>
      `
    })
    app.mount('#root')

2-4 数据,方法,计算属性和侦听器

    /**
     * data methods computed watcher
     * computed和methods实现相同的功能时,选computed,因为有缓存
     * computed和watcher实现相同的功能时,选computed,因为更简洁
     */
    const app = Vue.createApp({
        data() {
            return {
                message: 'hello world',
                count: 2,
                price: 5,
                newTotal: 10,
            }
        },
        methods: {
            // 方法中this指向vue实例
            // 不要使用箭头函数,因为箭头函数的this指向其外层的this
            handleClick() {
                console.log('click', this)
            },
            formatString(string) {
                return string.toUpperCase()
            },
            // 只要页面重新渲染,才会重新计算
            getTotal() {
                return Date.now()
                // return this.count * this.price
            }
        }, computed: {
            total() {
                // 当计算属性依赖的内容发生变更时,才会重新执行计算
                return Date.now()
                // return this.count * this.price
            }
        },
        // 当遇到异步情况的时候,可以使用watch
        // watch可以监听变量的改变,进行异步操作
        watch: {
            // price发生变化时函数会执行
            // price() {
            //     setTimeout(() => {
            //         console.log('price change')
            //     }, 1000)
            // },

            // 也可以实现计算属性的功能,实际上watch就是计算属性的底层实现
            price(cur, pre) {
                console.log(cur, pre)
                this.newTotal = cur * this.count
            }
        },
        template: `
          <!-- methods中的函数既可以在绑定事件的时候使用,也可以在js表达式{{}}中使用-->
          <div @click="handleClick">{{ formatString(message) }}</div>
          <!-- computed:计算属性
          	   当计算属性computed和方法methods实现的效果相同时,选computed,计算属性内部有缓存机制,会更高效
          -->
          <div>{{ message }} total:{{ total }}   getTotal():{{ getTotal() }}</div>
          <!-- 计算属性computed和监听watch实现的效果相同时,选computed -->
          <div>newTotal:{{ newTotal }}</div>
		`
    })
    const vm = app.mount('#root')

2-5 样式绑定语法

   const app = Vue.createApp({
       data() {
           return {
               classString: 'red',
               classObject: {red: true, green: true}, // 变量名:展示的样式值,变量值:是否展示
               classArray: ['red', 'green', {brown: true}],
            styleString: 'color: yellow', // 不建议使用
            styleObject: { // 建议使用对象的形式来写
                   color: 'orange',
               background: 'skyblue'
            }
           }
       },
       template: `
         <!-- 1.通过字符串方式,改变样式颜色 -->
         <div v-bind:class="classString">hello world</div>
         <!-- 对象 -->
         <div v-bind:class="classObject">hello world</div>
         <!-- 数组-->
         <div :class="classArray">hello world</div>

         <!-- 2.div父组件,demo子组件 -->
         <div :class="classString">
          hello world
          <!--
           demo组件上有两个标签,直接在demo上设置属性不能用
           解决办法:1)分别给one two写class属性
           2)给需要设置属性的标签上写:class="$attrs.class",使其去获取父组件的属性
          -->
          <demo class="green"/>
         </div>
         <!-- 3.行内样式 -->
         <!-- 1)直接在行内样式写 -->
         <div style="color: yellow">hello world</div>
         <!-- 2)与styleString数据绑定 (不建议使用)-->
         <div :style="styleString">hello world</div>
         <!-- 3)使用对象的形式表示 (建议使用)-->
         <div :style="styleObject">hello world</div>
   `
   })

   app.component('demo', {
       template: `
      <div :class="$attrs.class">one</div>
      <div>two</div>
`
   })
   const vm = app.mount('#root')

2-6 条件渲染

const app = Vue.createApp({
    data() {
        return {
            show: true,
         conditionOne: false,
         conditionTwo: true,
        }
    },
 template: `
   <!-- v-if & v-show -->
   <!-- v-if通过控制dom来展示元素,如果show=false,直接删除该div -->
  <div v-if="show">hello world</div>
   <!-- v-show通过控制style属性来展示元素,如果show=false,style="display: none" -->
  <div v-show="show">hello world</div>
   <!-- 总结:如果需要频繁改变dom元素显示与否,选v-show -->

   <!--if / else if /else-->
      <div v-if="conditionOne">if</div>
      <div v-else-if="conditionTwo">else</div>
      <div v-else>else</div>
 `
})
const vm = app.mount('#root')

2-7 列表循环渲染

    const app = Vue.createApp({
        data() {
            return {
                listArray: ['hello', 'world'],
                listObject: {
                    name: 'mys',
                    job: 'student'
                },
            }
        },
        methods: {
            handleAddBtnClick() {
                // 1.使用数组的变更函数 push pop shift unshift splice sort reverse
                // this.listArray.push('hello')
                // 2. 直接替换数组
                // this.listArray = ['bye', 'world'].filter(item => item === 'bye')
                // 3.直接更新数组的内容
                // this.listArray[1] = 'hhhh'

                // 直接添加对象的内容,也可以自动的展示出来
                this.listObject.age = '18'
                this.listObject.sex = 'female'
            }
        },
        template: `
          <div>
          <!-- 改变值 -->
          <!-- :key:表示是否可以复用,最好是唯一值 -->
          <div v-for="(item, index) in listArray" :key="index">
            {{ item }} -- {{ index }}
          </div>
          <button @click="handleAddBtnClick">新增1</button>

          <!--
          		for循环的优先级高于if,同时写if不生效
          		可以用一个占位符template写v-for
          		再用一个div写v-if
          -->
          <div>
            <!-- template占位符,不会在页面上做任何渲染 -->
            <template
                v-for="(value, key, index) in listObject"
                :key="index"
            >
              <div v-if="key !== 'name'">
                {{ key }} -- {{ value }} -- {{ index }}
              </div>
            </template>
          </div>
          <button @click="handleAddBtnClick">新增2</button>

          <div v-for="item in 5">{{ item }}</div>
          <button @click="handleAddBtnClick">新增3</button>
          </div>
		`
    })
    const vm = app.mount('#root')

2-8 事件绑定

      template: `
        <div>
         {{ counter }}
         <!-- 1.event原生事件,如果函数需要传递其他参数和原生event,可以使用$event传递原生事件 -->
         <!-- <button @click="handleBtnClick(2, $event)">button</button>-->
         <!-- 2.如果事件绑定多个函数,函数间可以用,隔开,注意需要加() -->
         <!-- <button @click="handleBtnClick1(), handleBtnClick2()">button</button>-->
         <!--
         3.事件修饰符:
         .stop     停止冒泡
         .self     自身标签出发才有效,子标签触发无效
         .prevent   阻止默认行为
         .capture   把事件的运营模式变成捕获模式
         .once     事件的绑定只执行一次
         .passive
     -->
         <div @click.self="handleDivClick">
           div自身
           <button @click.stop="handleBtnClick">button</button>
         </div>
         <!--
               4.按键修饰符:
                   .enter 按下enter才执行函数
                   .tab/.delete/.esc/.up ......
          -->
         <input @keydown.enter="handleKeyDown"/>
         <!--
               5.鼠标修饰符:
                   .left/.right/.middle
          -->
         <div @click.left="handleClick">鼠标修饰符</div>
         <!--
               6.精确饰符:
                   .exact  只有精确按下指定的键才生效
          -->
         <div @click.ctrl.exact="handleClick">精确饰符</div>
        </div>
`
  })

2-9 表单中双向绑定指令的使用

    const app = Vue.createApp({
        data() {
            return {
                message: 'hello',
                checkbox: [],
                radio: '',
                select: '',
                options: [{
                    text: 'A', value: 'A',
                }, {
                    text: 'B', value: 'B',
                }, {
                    text: 'C', value: {value: 'C'},
                }],
                checkboxValue: 'hello',
	            lazy: 'lazy',
	            number: 12,
            }
        },
        template: `
          <div>
	          <!--
	                1.v-model:双向绑定  输入框与变量message绑定
	                input/textarea/checkbox/radio/select
	          -->
	          {{ message }}
	          <input v-model="message"/>
	          <textarea v-model="message"/>
	          {{ checkbox }}
	          <!-- checkbox:可以写多个,可以用一个数组来保存选中的value -->
	          aa <input type="checkbox" v-model="checkbox" value="aa"/>
	          bb <input type="checkbox" v-model="checkbox" value="bb"/>
	          {{ radio }}
	          cc <input type="radio" v-model="radio" value="cc"/>
	          dd <input type="radio" v-model="radio" value="dd"/>
	          {{ select }}
	          <select v-model="select" multiple>
	            <!-- 不加multiple可以写下面的提示 -->
	            <!-- <option disabled value="">请选择内容</option>-->
	            <!-- options中的value也可以是一个对象 -->
	            <option v-for="item in options" :value="item.value">{{ item.text }}</option>
	          </select>
	          {{ checkboxValue }}
	          <!-- 使用true-value/false-value可以改变checkbox的值 -->
	          <input type="checkbox" v-model="checkboxValue" true-value="hello" false-value="world"/>
	
	          <!--
	                2.修饰符
	                    .lazy 		当input框失去焦点时才生效
	                    .number 	将输入框的类型变为number
	                    .trim 		去除字符串前后空格
	           -->
	          {{ lazy }}
	          <input v-model.lazy="lazy"/>
	          {{ typeof number }}
	          <input v-model.number="number"/>
          </div>
		`
    })
    const vm = app.mount('#root')

3 探索组件的理念

3-1 组件的定义及复用性,局部组件和全局组件

在这里插入图片描述

 /**
  * 定义一个局部组件,在app中不能直接使用,
  * 需要写上:components: {'mys': counter_part}引入局部组件
  * 定义局部组件,通常将首字符大写
  */
 const CounterPart = {
     data() {
         return {
             count: 1
         }
     },
     template: `<div @click="count += 1">{{count}}</div>`
 }

 const HelloWorld = {
     template: `<div>hello world</div>`
 }

 const app = Vue.createApp({
     /**
      * 创建一个vue实例,渲染根组件
      * 组件:对页面的展示
      * 组件可以被复用,且其数据相互独立
      * 组件在app中定义了,即使没使用,也会挂在app上
      * 在app中定义的组件是全局组件,在处处都能使用,性能不高,使用简单,名字通常用小写字母,中间用-隔开
      * 局部组件需要定义并注册后才能使用,性能高,但是使用麻烦,名字通常大写字母开头,驼峰式命名
      * 局部组件使用时,需要名字和组件间映射对象,如果不写映射,按照上面的命名方式,vue底层会自动映射
      */

     // 在vue实例中使用components引入局部组件
     components: {
         // 'CounterPart': CounterPart,
         // CounterPart 命名和组件名相同可以简写
      // 'hello-world': HelloWorld

      // vue会自动进行映射,将驼峰命名格式映射为:counter-part短横线分割的格式
      CounterPart, HelloWorld
     },
     template: `
<div>
   <!-- 全局组件 -->
   <hello/>
   <world/>
   <counter-parent/>
   <counter/>
   <counter/>

   <!-- 局部组件 -->
   <counter-part/>
   <hello-world/>
</div>
`
 })

 // 定义子组件 (全局组件)
 app.component('hello', {
     template: `<div>hello</div>`
 })
 app.component('world', {
     template: `<div>world</div>`
 })
 app.component('counter-parent', {
     template: `<counter/>`
 })
 app.component('counter', {
     data() {
         return {
             count: 1
         }
     },
     template: `<div @click="count += 1">{{count}}</div>`
 })
 const vm = app.mount('#root')

3-2 组件间传值及传值校验

  const app = Vue.createApp({
      data() {
          return {
              num: 123,
              // num: () => alert('num')
          }
      },
      template: `
        <div>
        <!--
       1.父组件调用子组件,传递内容给子组件使用
       2.静态参数 & 动态参数
       静态传参:不带:,直接使用,一般只能传递字符串
       动态传参:带:,比如 :content,由data里面的数据决定
  -->
           <test :content="num"/>
        </div>
`
  })
  /**
   * 3.父组件->子组件:接收数据、类型校验
   * type:String,Boolean,Array,Object,Function,Symbol
   * required: 必选
   * default:默认值
   */
  // 创建一个全局组件
  app.component('test', {
      // props: ['content'], 直接接收父组件传递来的数据
      // 如果需要对父组件传递来的数据做验证
      props: {
          // 如果传递来的数据不是String类型的,控制台会出现警告提示:type check failed for prop "content"
          // content: String,
          // content:Function,

       content: {
            type: Number,
        // default: 123, // 默认值
        // required: true, // 如果没写default,不传参会有警告

        // 深度校验
              validator: function (value) { // value父组件传递来的值
                  return value < 1000
              },
        // default也可以是一个函数
              default: function () {
                  return 456
              }
       },
      },
      methods: {
          handleClick() {
              alert('handleClick')
           this.content() // 调用父组件传递来的函数
          }
      },
      template: `
        <div @click="this.handleClick">{{ content }}</div>`
  })

  const vm = app.mount('#root')

3-3 单向数据流的理解

  const app = Vue.createApp({
      data() {
          return {
              params: {
                  content: 123,
                  a: 1,
                  b: 2,
              },
              dataAbc: 123,
              num: 1,
          }
      },
      template: `
        <div>
        <!-- <test :content="num" :a="a" :b="b"/> -->
        <!-- 1.如果参数太多,可以把参数封装到一个对象params中 -->
        <!--  <test v-bind="params"/>等价于:
              <test :content="params.content" :a="params.a" :b="params.b"/>
        -->
        <test v-bind="params"/>

        <!-- 2.如果参数名太长,用-分隔,不能使用驼峰命名,但是注意:子组件接收参数时需要用驼峰命名
              即:属性传时使用:data-abc  接收时:dataAbc
        -->
        <test data-abc="123"/>

        <counter :count="num"/>
        </div>
`
  })
  app.component('test', {
      props: ['content', 'a', 'b', 'dataAbc'],
      template: `
        <div>{{ content }} -- {{ a }} -- {{ b }} -- {{ dataAbc }}</div>
`
  })

  /**
   * 3.单向数据流
   * 父组件可以向子组件传递数据,但数据是只读的,子组件不能修改
   * 避免组件之间的数据耦合
   */
  app.component('counter', {
      props: ['count'],
   data() {
          return {
              myCount: this.count
          }
   },
      template: `
        <!-- <div @click="count += 1">{{ count }}</div> 不能修改父组件传递过来的数据-->
        <div @click="myCount += 1">{{ myCount }}</div>
      `
  })
  const vm = app.mount('#roo

3-4 Non-Props 属性是什么

 /**
  * Non-props属性:
  * 父组件给子组件传递数据时,子组件不通过props接收,
  * 此时底层会将父组件传递过来的属性放在最外层div属性上,
  * 如果在子组件上写:inheritAttrs: false,就不会放在最外层div上
  * 使用:通常用于 style class属性
  */
 const app = Vue.createApp({
  template: `
   <div>
       <counter msg="hello" msg2="world"/>
</div>
  `
 })
 app.component('counter', {
     // inheritAttrs: false,
  mounted() {
         console.log(this.$attrs.msg) // hello
  },
     template: `
   <!-- 如果有多个div,Non-prop属性就不会生效,需要使用v-bind="$attrs"决定是否接收属性
      v-bind="$attrs":    接收所有属性
      :msg="$attrs.msg":  接收指定属性
   -->
   <div v-bind="$attrs">Counter</div>
   <div :msg="$attrs.msg">Counter</div>
   <div>Counter</div>
`,
 })
 const vm = app.mount('#root')

3-5 父子组件间如何通过事件进行通信

 /**
  * 父子组件通信的流程:
  * 1.子组件接收父组件传递来的count
  * 2.点击时触发了事件:handleClick,该事件向父组件传递:add事件,也能传递参数
  * 3.父组件监听到add事件,通过handleAdd进行处理,修改count
  * 4.最后修改了的count传递给了子组件
  */
 const app = Vue.createApp({
  data() {
       return{
           count: 1
       }
  },
  methods: {
         handleAdd(param) {
             this.count += param
         }
  },
     template: `
   <div>
       <!-- 注意:触发事件时:驼峰命名,监听时间:-分隔命名 -->
       <counter :count="count" @add="handleAdd"/>
</div>
  `
 })
 app.component('counter', {
     props: ['count'],
  // emits:表示触发了哪些事件
  emits: ['add'],
  // embeds: {
     //     // 可以对参数进行校验
     //     add: (count) => {
     //         if (count > 0) {
     //             return true
     //         }
     //         return false
     //     }
  // },
  methods: {
         handleClick() {
             this.$emit('add', 2)
          // 事件处理的逻辑也可以直接在子组件内完成,再传递给父组件
             // this.$emit('add', this.count + 3)
         }
  },
     template: `
       <div @click="handleClick">{{ count }}</div>
     `,
 })
 const vm = app.mount('#root')

使用v-model简化

  /**
   * 使用v-model简化
   * 1.父组件中定义一个数据count,通过v-model传递给子组件
   * 2.子组件使用modelValue接收参数,注意:接收参数名只能是:modelValue
   * 3.点击触发事件handleClick,该事件向父组件传递的事件名只能是:update:modelValue
   *  传递的参数modelValue会替换父组件的count
   */
  const app = Vue.createApp({
      data() {
          return {
              count: 1
          }
      },
      template: `
        <counter v-model="count"/>
        <!-- 如果想要指定modelValue的名字,可以使用:app,下面全部改成app即可 -->
        <!-- <counter v-model:app="count"/> -->
`
  })
  app.component('counter', {
      props: ['modelValue'],
      methods: {
          handleClick() {
              this.$emit('update:modelValue', this.modelValue + 3)
          }
      },
      template: `
        <div @click="handleClick">{{ modelValue }}</div>
`,
  })
  const vm = app.mount('#root')

3-6 组件间双向绑定高级内容

**1、父子组件间的通信 v-model 传递多个参数 **

    /**
     * 父子组件间的通信 v-model
     *  传递多个参数
     */
    const app = Vue.createApp({
        data() {
            return {
                count: 1,
	            count1: 1,
            }
        },
        template: `
          <!-- 如果需要传递2个参数,可以写2个v-model,并通过:count的方式指定名字 -->
          <counter v-model:count="count" v-model:count1="count1"/>
		`
    })
    app.component('counter', {
        props: ['count', 'count1'],
        methods: {
            handleClick() {
                this.$emit('update:count', this.count + 3)
            },
            handleClick1() {
                this.$emit('update:count1', this.count1 + 3)
            }
        },
        template: `
          <div @click="handleClick">{{ count }}</div>
          <div @click="handleClick1">{{ count1 }}</div>
		`,
    })
    const vm = app.mount('#root')

2、修饰符的使用

  /**
   * 父子组件间的通信 v-model
   *  修饰符的使用:
   *      .uppercase
   *
   * 1.父组件中:v-model.uppercase
   * 2.父组件会传递一个modelModifiers参数接收修饰符,注意:modelModifiers名字是固定的
   * 3.在点击事件handleClick,获取修饰符的值,并调用相应的函数进行处理
   * 4.向父组件传递事件:update:modelValue和新的参数newValue
   * 5.父组件获取到事件后,对count变量的值进行修改
   */
  const app = Vue.createApp({
      data() {
          return {
              count: 'a',
          }
      },
      template: `
        <counter v-model.uppercase="count"/>
`
  })
  app.component('counter', {
      props: {
          'modelValue': String,
          // 传递过来的修饰符
          'modelModifiers': {
              // 如果不传修饰符,默认空对象;如果传了修饰符会放到对象中
              default: () => ({})
          }
      },
      // mounted() {
      //     console.log(this.modelModifiers)
      // },
      methods: {
          handleClick() {
              let newValue = this.modelValue + 'b'
              if (this.modelModifiers.uppercase) {
                  newValue = newValue.toUpperCase()
              }
              this.$emit('update:modelValue', newValue)
          },
      },
      template: `
        <div @click="handleClick">{{ modelValue }}</div>
`,
  })
  const vm = app.mount('#root')

3-7 使用插槽和具名插槽解决组件内容传递问题

    /**
     * slot 插槽
     * slot不能绑定事件,可以在用一个span标签包裹slot,用于绑定事件
     * slot中可以传递:标签、字符串、子组件
     *
     * slot中使用的数据作用域问题:
     * 父模板里调用的数据属性,都用的是父模板里的数据
     * 子模板里调用的数据属性,都用的是子模板里的数据
     * 
     * slot的简写:v-slot:header => #header
     */
    const app = Vue.createApp({
        data() {
            return {
                text: '提交',
            }
        },
        template: `
          <!--
			  需求:第1个myform:提交组件是 div;第2个myform:提交组件是 button
					  将myform变成双标签,在标签内写入需要的东西
					  可以将需要的内容放入data中,再获取
		   -->
          <myform>
          <!-- slot -->
          	<div>{{ text }}</div>
          </myform>
          <myform>
          	<button>{{ text }}</button>
          </myform>
          <myform>
          </myform>

          <layout>
          	<!-- 具名插槽:
          		用占位符template指定不同的slot
          	-->
          	<template v-slot:header>
	          <div>header</div>
         	 </template>
            <!-- slot简写 -->
            <template #footer>
              <div>footer</div>
            </template>
          </layout>
		`
    })
    app.component('myform', {
        methods: {
            handleClick() {
                alert('click')
            }
        },
        template: `
          <div>
          <input/>
          <span @click="handleClick">
	            <!-- 可以设定默认值 -->
	          	<slot>default value</slot>
	          </span>
          </div>
		`,
    })

    app.component('layout', {
        template: `
            <div>
                <slot name="header"></slot>
                <div>content</div>
                <slot name="footer"></slot>
			</div>
        `
    })
    const vm = app.mount('#root')

3-8 作用域插槽

    /**
     * 作用域插槽
     * 1.父组件调用list的子组件,即传递给子组件的slot div
     * 2.子组件调用slot,通过div的形式循环显示内容
     * 3.调用slot时,将item数据通过slotProps数据对象传递给slot
     *
     * 当子组件渲染的内容由父组件决定时
     * 通过作用域插槽,能够让父组件调用子组件的数据
     */
    const app = Vue.createApp({
        template: `
<!--           <list v-slot="slotProps"> -->
<!--             <div>{{ slotProps.item }}</div> -->
<!--           </list> -->
         <!-- 可以使用解构的方式简化 -->
          <list v-slot="{item}">
            <div>{{ item }}</div>
          </list>
      `
    })

    app.component('list', {
        data() {
            return {list: [1, 2, 3]}
        },
        template: `
          <div>
           <slot v-for="item in list" :item="item"/>
          </div>
      `
    })
    const vm = app.mount('#root')

3-9 动态组件和异步组件

  /**
   * 需求:通过一个变量控制input-item、common-item的展示与隐藏
   *
   * 动态组件:根据数据的变化,结合component标签,来随时动态切换组件的显示
   * 异步组件:异步执行某些组件的逻辑
   */

  const app = Vue.createApp({
      data() {
          return {currentItem: 'input-item'}
      },
      methods: {
          handleClick() {
              this.currentItem = this.currentItem === 'input-item' ? 'common-item' : 'input-item'
          }
      },
      template: `
        <!--          <input-item v-show="currentItem === 'input-item'"/>-->
        <!--          <common-item v-show="currentItem === 'common-item'"/>-->
        <!-- keep-alive缓存,component动态组件 -->
       <keep-alive>
           <component :is="currentItem"/>
       </keep-alive>
       <button @click="handleClick">切换</button>

        <div>
         <sync-common-item /> <!-- 同步组件 -->
         <async-common-item /> <!-- 异步组件 -->
        </div>

		`
  })

  app.component('input-item', {
      template: `
        <input />
		`
  })
  app.component('common-item', {
      template: `
        <div>hello world</div>
`
  })

  // 同步
  app.component('sync-common-item', {
      template: `
        <div>hello world</div>
`
  })
  // 异步
  app.component('async-common-item', Vue.defineAsyncComponent(() => {
      return new Promise((resolve, reject) => {
          setTimeout(() => {
              resolve({
                  template: `<div>this is an async component</div>`
              })
          }, 4000)
      })
  }))
  const vm = app.mount('#root')

3-10 基础语法知识点查缺补漏

  /**
   * v-once:让某个元素标签只渲染一次
   * ref:获取dom节点 / 组件的一个用法 (直接操作dom是要慎重)
   * provide / inject 多层组件传值,使用provide提供数据,inject注入数据
   */
  const app = Vue.createApp({
      data() {
          return {count: 1}
      },
   // provide: {
      //     count: 2,
   // },
   // 如果是传递data中的数据,要使用函数形式
   // 此处传递的数据是一次性的,需要类似双向绑定的效果需要响应的知识,后面会讲
   provide() {
          return {
              count: this.count
          }
   },
      mounted() {
          // 获取dom最好在mounted中,因为此时页面已经加载完成。但是最好不要直接操作dom
          // console.log(this.$refs.count.innerHTML = 'hello')
          // console.log(this.$refs.common.syaHello())
      },
      template: `
        <div>
         <div ref="count">
           {{ count }}
         </div>
             <common-item ref="common"/>

         <!-- 层层传递数据count:父组件->child->child-child -->
             <child :count="count"/>
            <button @click="count += 1">Add</button>
        </div>

`
  })
  app.component('common-item', {
      methods: {
          syaHello() {
              alert('hello')
          }
      },
   template: `<div>hello world</div>`
  })
  app.component('child', {
      props: ['count'],
      // template: `<child-child :count="count"/>`
      template: `<child-child/>`
  })
  app.component('child-child', {
      // props: ['count'],
   inject: ['count'],
      template: `<div>{{ count }}</div>`
  })
  const vm = app.mount('#root')

4 Vue 中的动画

4-1 使用 Vue 实现基础的 CSS 过渡与动画效果

<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <title>4-1</title>
   <script src="https://unpkg.com/vue@next"></script>
   <style>
        /* 动画 */
        .animation {
            animation: leftToRight 3s;
        }

        @keyframes leftToRight {
            0% {
                transform: translateX(-100px);
            }
            50% {
                transform: translateX(-50px);
            }
            0% {
                transform: translateX(0px);
            }
        }

        /* 过渡 */
        .transition {
            transition: 3s background-color ease;
        }

        .blue {
            background-color: blue;
        }

        .green {
            background-color: green;
        }

   </style>
</head>
<body>
<div id="root"></div>
</body>
<script>
    // 过渡 动画
    const app = Vue.createApp({
        data() {
            return {
                animate: {
                    animation: false
                },
                transit: {
                    transition: true,
                    blue: true,
                    green: false,
                },
                styleObj: {
                    background: 'red'
                }
            }
        },
        methods: {
            handleClick() {
                this.animate.animation = !this.animate.animation
            },
            handleClick1() {
                this.transit.transition = !this.transit.transition
                this.transit.blue = !this.transit.blue
                this.transit.green = !this.transit.green
            },
            handleClick2() {
                if (this.styleObj.background === 'red') {
                    this.styleObj.background = 'yellow'
                } else {
                    this.styleObj.background = 'red'
                }
            }
        },
        template: `
          <div>
             <!-- 动画 -->
             <div :class="animate">hello</div>
             <button @click="handleClick">切换</button>
             <!-- 过渡:css -->
             <div :class="transit">hello</div>
             <button @click="handleClick1">切换</button>
             <!-- 过渡:style样式 -->
             <div class="transition" :style="styleObj">hello</div>
             <button @click="handleClick2">切换</button>
          </div>
      `
    })
    const vm = app.mount('#root')
</script>
</html>

4-2 使用 transition 标签实现单元素组件的过渡和动画效果

<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <title>4-2</title>
   <script src="https://unpkg.com/vue@next"></script>
   <!-- 引入动画样式库 -->
   <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"/>
   <style>
        /*入场动画:v-enter-from/to/active*/
        .v-enter-from {
            opacity: 0;
            color: red;
        }

        .v-enter-to {
            opacity: 1;
        }

        /*出场动画*/
        /*可以不写,因为出厂前opacity本身就是1*/
        /*.v-leave-from {*/
        /* opacity: 1;*/
        /*}*/
        .v-leave-to {
            opacity: 0;
        }

        /*在整个动画的过程中如何执行:入场、出场效果一样,可以写在一起*/
        .v-enter-active,
        .v-leave-active,
        .hello,
        .bye {
            animation: shake 3s;
            transition: all 3s ease-in;
        }

        /*动画*/
        @keyframes shake {
            0% {
                transform: translateX(-100px);
            }
            50% {
                transform: translateX(-50px);
            }
            100% {
                transform: translateX(0px);
            }
        }
   </style>
</head>
<body>
<div id="root"></div>
</body>
<script>
    /**
     * 单元素、单组件的入场、出场动画,可以给组件同时添加动画、过渡效果
     */
    const app = Vue.createApp({
        data() {
            return {
                show: true
            }
        },
        methods: {
            handleClick() {
                this.show = !this.show
            },
            // 在入场之前可以做的事
            handleBeforeEnter(el) {
                el.style.color = 'red'
            },
           // 执行时的操作
            handleEnterActive(el, done) {
                const animation = setInterval(() => {
                    const color = el.style.color
                    if (color === 'red') {
                        el.style.color = 'yellow'
                    } else {
                        el.style.color = 'red'
                    }
                }, 1000)
                // 5s后清除动画
                setTimeout(() => {
                    clearInterval(animation)
                   done() // 表明动画结束
                }, 5000)
            },
           // 执行完的操作
            handleEnterEnd() {
                alert(123)
            },
        },
        template: `
          <div>
             <!--
               1.可以在transition中重命名:<transition name="hello">
                  .v-enter-active => .hello-enter-active
                  固定写法:.v-enter-from  .v-enter-active  .v-enter-to
                        .v-leave-from  .v-leave-active  .v-leave-to
               2.也可以自定义名字:
                  <transition
                           enter-active-class="hello"
                           leave-active-class="bye"
                       >
                       在style中直接使用.hello / .bye即可
           -->
             <!--          <transition-->
             <!--              enter-active-class="hello"-->
             <!--              leave-active-class="bye"-->
             <!--          >-->

             <!-- 3.可以直接引入第三方animate.css的动画库: https://animate.style/-->
             <!--          <transition-->
             <!--              enter-active-class="animate__animated animate__bounce"-->
             <!--              leave-active-class="animate__animated animate__bounce"-->
             <!--          >-->

             <!--
                 4.type="transition":当同时有过渡和动画效果时,如果时间不一致,可以指定以其中的一个为准,控制同步执行
                 (1)type="transition" 以transition为准
                 (2):duration="1000":指定执行时间
                   :duration="{enter: 1000, leave:3000}" 也可以分别指定时间
            -->
             <!-- <transition :duration="1000"> -->

             <!--
                   5.如果想用js来控制动画效果,可以取消css控制  :css="false"
                       钩子:生命周期函数,在某个时刻被调用
                           @before-enter="handleBeforeEnter"   函数参数:el
                           @enter="handleEnterActive"             el done
                           @after-enter="handleEnterEnd"           el

                           @before-leave
                           @leave
                           @after-leave
             -->
             <transition
                 :css="false"
                 @before-enter="handleBeforeEnter"
                 @enter="handleEnterActive"
                 @after-enter="handleEnterEnd"
             >

               <div v-if="show">hello world</div>
               <!-- <div v-show="show">hello world</div>-->
             </transition>
             <button @click="handleClick">切换</button>
          </div>
      `
    })
    const vm = app.mount('#root')
</script>
</html>

4-3 组件和元素切换动画的实现

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>4-2</title>
  <script src="https://unpkg.com/vue@next"></script>
  <style>
    .v-enter-from {
      opacity: 0;
    }
    .v-enter-active,
    .v-leave-active {
      transition: opacity 3s ease-in;
    }
    .v-enter-to {
      opacity: 1;
    }

    .v-leave-from {
      opacity: 1;
    }
    .v-leave-to {
      opacity: 0;
    }

  </style>
</head>
<body>
<div id="root"></div>
</body>
<script>
  /**
   * 多个单元素标签之间的切换 transition
   *
   * 多个单组件标签之间的切换
   * 1.v-if v-else
   * 2.动态组件 <component :is="component" />
   */
  const ComponentA = {
    template: `<div>hello world</div>`
  }
  const ComponentB = {
    template: `<div>bye world</div>`
  }

  const app = Vue.createApp({
    data() {
      return {
        show: false,
        component: 'component-a',
      }
    },
    components: {
      'component-a': ComponentA,
      'component-b': ComponentB,
    },
    methods: {
      handleClick() {
        this.show = !this.show
      },
      handleClick1() {
        if (this.component === 'component-a') {
          this.component = 'component-b'
        } else {
          this.component = 'component-a'
        }
      },
    },
    template: `
          <div>
              <!-- mode="out-in":动画效果先隐藏再展示
                   appear:初次展示动画效果
              -->
             <transition mode="out-in" appear>
                <div v-if="show">hello world</div>
                <div v-else="show">bye world</div>
             </transition>
              <button @click="handleClick">切换</button>

              <transition mode="out-in" appear>
                <component-a v-if="show" />
                <component-b v-else="show" />
              </transition>
             <button @click="handleClick">切换</button>

              <transition mode="out-in" appear>
                <component :is="component" />
              </transition>
             <button @click="handleClick1">切换</button>
          </div>
      `
  })
  const vm = app.mount('#root')
</script>
</html>

4-4 列表动画

<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <title>4-4</title>
   <script src="https://unpkg.com/vue@next"></script>
   <style>
       .list-item {
          display: inline-block;
          margin: 10px;
       }

       .v-enter-from,
       .v-leave-to {
          opacity: 0;
          transform: translateY(30px);
       }
       .v-enter-active,
       .v-leave-active {
          transition: all 1s ease-in;
       }
       .v-enter-to,
       .v-leave-from {
          opacity: 1;
           transform: translateY(0px);
       }

       /*其他列表项的移动效果*/
       .v-move {
          transition: all 1s ease-in;
       }
   </style>
</head>
<body>
<div id="root"></div>
</body>
<script>
  /**
   * 列表动画
   * transition-group
   * .v-move
   */

  const app = Vue.createApp({
    data() {
      return {
        list: [1, 2, 3],
      }
    },
    methods: {
      handleClick() {
        this.list.unshift(this.list.length + 1)
      },
    },
    template: `
      <div>
      <transition-group>
         <span class="list-item" v-for="item in list" :key="item">{{ item }}</span>
         <button @click="handleClick">增加</button>
      </transition-group>
      </div>
   `
  })
  const vm = app.mount('#root')
</script>
</html>

4-5 状态动画

 /**
  * 状态动画 svg
  */

 const app = Vue.createApp({
   data() {
     return {
       number: 1,
       animateNumber: 1,
     }
   },
   methods: {
     handleClick() {
       this.number = 10
       if (this.animateNumber < this.number) {
         const animation = setInterval(() => {
           this.animateNumber += 1
           if (this.animateNumber === 10) {
             clearInterval(animation)
           }
         }, 100)
       }
     },
   },
   template: `
     <div>
      <div>{{ animateNumber }}</div>
      <button @click="handleClick">增加</button>
     </div>
`
 })
 const vm = app.mount('#root')

5 Vue 中的高级语法

5-1 Mixin 混入的基础语法

 /**
  * mixin 混入 (vue3引入后不推荐)
  * 1.组件data,methods 优先级高于mixin data,methods优先级
  * 2.生命周期函数先执行mixin中的,再执行组件中的
  * 3.自定义的属性,组件的属性优先级高于mixin属性的优先级
  * 4.改变mixin优先级
  *
  * 局部 mixin
  * 全局 mixin(不推荐)
  */
   // 局部mixin
 const myMixin = {
     // data() {
     //   return {
     //     number: 2,
     //   }
     // },
     number: 2,
     created() {
       console.log('mixin created')
     },
     methods: {
       handleClick() {
         console.log('mixin handleClick')
       }
     },
   }

 const app = Vue.createApp({
   // data() {
   //   return {
   //     number: 1,
   //   }
   // },
   number: 1,
   mixins: [myMixin], // 使用mixin
   created() {
     console.log('created')
   },
   methods: {
     // handleClick() {
     //   console.log('handleClick')
     // }
   },
   template: `
     <div>
     <!--      <div>{{ number }}</div>-->
     <!--      <button @click="handleClick">点击</button>-->

     <!-- vue中所有没有被处理的内容都会被挂在options中 -->
     <div>{{ this.$options.number }}</div>
     <child/>
     </div>
`
 })

 // 修改mixin属性的优先级
 app.config.optionMergeStrategies.number = (mixinVal, appValue) => {
   return mixinVal || appValue
 }

 // 全局mixin
 // app.mixin({
 //   data() {
 //     return {
 //       number: 2,
 //     }
 //   },
 //   created() {
 //     console.log('mixin created')
 //   },
 //   methods: {
 //     handleClick() {
 //       console.log('mixin handleClick')
 //     }
 //   },
 // })

 app.component('child', {
   template: "<div>child</div>"
 })
 const vm = app.mount('#root')

5-2 开发实现 Vue 中的自定义指令

  /**
   * 自定义组件 directive
   * 1.局部
   *    (1) 定义指令
   *    (2) 在app中使用指令
   * 2.全局
   *    app.directive
   */

    // 局部自定义指令
    // const directives = {
    //     focus: {
    //       mounted(el) {
    //         el.focus()
    //       }
    //     }
    //   }

  const app = Vue.createApp({
      data() {
        return {show: true}
      },
      // directives,
      template: `
        <div>
        <div v-if="show">
          <input v-focus/>
        </div>
        </div>
	  `
    })

  // 全局自定义指令
  app.directive('focus', {
    // 执行前
    beforeMount() {
      console.log('beforeMount')
    },
    // 执行
    mounted(el) {
      el.focus()
    },
    // 更新前
    beforeUpdate() {
      console.log('beforeUpdate')
    },
    // 更新
    updated() {
      console.log('updated')
    },
    // 失效前
    beforeUnmount() {
      console.log('beforeUnmount')
    },
    // 失效
    unmounted() {
      console.log('unmounted')
    }
  })
  const vm = app.mount('#root')

小例子

/**
 * 小例子:控制输入框的位置
 */
const app = Vue.createApp({
    data() {
      return {distance: 100}
    },
    template: `
      <div>
      <!-- 参数binding.arg=left  参数的值:binding.value=distance -->
      <div v-pos:left="distance" class="header">
        <input/>
      </div>
      </div>
 `
  })

// app.directive('pos', {
//   mounted(el, binding) {
//     el.style.top = binding.value + 'px'
//   },
// })
// 如果只有一个函数,可以简写
app.directive('pos', (el, binding) => {
  el.style[binding.arg] = (binding.value + 'px')
})
const vm = app.mount('#root')
.header {
   position: absolute;
}

5-3 Teleport 传送门功能

 /**
  * teleport:传送门
  */
 const app = Vue.createApp({
   data() {
     return {
       show: false
     }
   },
   methods: {
     handleBtnClick() {
       this.show = !this.show
     },
   },
   template: `
     <div class="area">
     <button @click="handleBtnClick">按钮</button>
     <!-- 传送门:使得mask挂载到body下 -->
     <teleport to="body">
       <div class="mask" v-show="show"></div>
     </teleport>
     </div>
`
 })
 const vm = app.mount('#root')
.area {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    width: 200px;
    height: 200px;
    background-color: skyblue;
}

/*蒙层*/
.mask {
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    bottom: 0;
    background-color: black;
    opacity: 0.5;
}

5-4 更加底层的 render 函数

 /**
  * render函数
  * template底层编译会生成render函数
  * template -> render -> h -> 虚拟DOM(JS对象) -> 真是DOM -> 展示到页面上
  */
 const app = Vue.createApp({
   template: `
     <my-title :level="2">
       hello mys
  </my-title>
`
 })
 app.component('my-title', {
   props: ['level'],
   render() {
     const { h } = Vue
     /**
      * 返回:虚拟DOM 1.让vue的性能更快 2.使得vue可以跨平台使用
      * 虚拟DOM eg.
      * <div>hello</div>
      * {
      *     tagName: 'div,  标签名
      *     text: hello,    标签属性
      *     attributes: {}  节点内容
      * }
      * 第三个参数也可以是一个数组,里面可以继续嵌套
      */
     // this.$slots.default():获取slot中的值 注意:slots要加上s
     // return h('h' + this.level, {}, this.$slots.default())
     return h('h' + this.level, {}, [this.$slots.default(),
     h('h4', {}, 'mys')])
   }
   // template: `
   //   <h1 v-if="level === 1"><slot /></h1>
   //   <h2 v-if="level === 2"><slot /></h2>
// `
 })
 const vm = app.mount('#root')

5-5 插件的定义和使用

 /**
  * plugin插件:把通用性的功能封装起来
  */
 const myPlugin = {
   // app:使用插件的vue实例 options:传递的对象
   // 实例拿到之后,可以做很多扩展
   install(app, options) {
     // console.log(app, options)
     app.provide('name', 'mys')
     app.directive('focus', {
       mounted(el) {
         el.focus()
       }
     })
     app.mixin({
       mounted() {
         // 会输出2次,根组件、子组件都会打印
         console.log('mixin')
       }
     })
     // 底层扩展全局属性
     app.config.globalProperties.$sayHello = 'hello world'
   }
 }
 const app = Vue.createApp({
   template: `
     <my-title />
`
 })
 app.component('my-title', {
   inject: ['name'], // 声明provide传递的参数
   mounted() {
     console.log(this.$sayHello)
   },
   template: `<div>{{name}}<input v-focus /></div>`
 })
 app.use(myPlugin, {name: 'mys'})
 const vm = app.mount('#root')

5-6 数据校验插件开发实例

 /**
  * 读数据做校验的插件
  */
 const app = Vue.createApp({
   data() {
     return {
       name: 'mys',
       age: 18
     }
   },
   // 校验
   rules: {
     age: {
       validate: age => age > 10,
       message: 'too young'
     },
     name: {
       validate: name => name.length >= 3,
       message: 'too short'
     }
   },
   template: `
     <div>name:{{ name }},age:{{ age }}</div>
`
 })

 const validatePlugin = (app, options) => {
   app.mixin({
     created() {
       for (let key in this.$options.rules) {
         const item = this.$options.rules[key]
         // this: app实例
         // 监听实例数据变化
         this.$watch(key, (value) => { // 注意:此处要传入value参数,表示修改后的值
           const result = item.validate(value)
           if (!result) console.log(item.message)
         })
       }
     }
   })
 }
 app.use(validatePlugin)

 const vm = app.mount('#root')

6 Composition API

6-1 Setup 函数的使用

 /**
  * Composition API:维护代码更方便
  */
 const app = Vue.createApp({
   data() {
     return {
       name: 'mys',
     }
   },
   /**
    * setup在实例被完全初始化之前执行
    * setup中不能使用this,因为实例还没有被初始化
    * setup不能调用外部实例方法,而实例方法可以调用setup
    */
   setup(props, context) {
     return {
       name: 'mys',
       handleClick: () => {
         console.log('handleClick')
       }
     }
   },
   template: `
     <div @click="handleClick">{{ name }}</div>
`
 })
 const vm = app.mount('#root')

6-2 ref,reactive 响应式引用的用法和原理

 /**
  * 响应式的引用
  * 原理:通过proxy对数据进行封装,当数据变化时触发模板等内容的更新
  * ref:处理基础类型的数据
  * reactive:处理非基础类型的数据
  *
  * 可以替代data
  */
 const app = Vue.createApp({
   template: `
     <!-- 底层判断出这是一个ref数据,会自动调用x.value -->
           <div>{{ name }}</div>
     <!--      <div>{{ nameObj.name }}</div>-->
     <!-- <div>{{ arr[0] }}  {{ copyArr[0] }}</div> -->
`,
   setup(props, context) {
     // 1. ref
     // const {ref} = Vue
     // // 通过proxy: 'mys' => proxy({value: 'mys'}) 一个响应式引用
     // let name = ref('mys')
     // setTimeout(() => {
     //   name.value = 'lee'
     // }, 2000)
     // return {name}

     // 2.reactive
     const {reactive, toRefs} = Vue
     // 通过proxy: {name: 'mys'} => proxy({name: 'mys'}) 一个响应式引用
     const nameObj = reactive({name: 'mys'})
     setTimeout(() => {
       nameObj.name = 'lee'
     }, 2000)
     // // return {nameObj}

     // 如果用解构,将nameObj中的name值返回,最后不会出现响应式,因为响应式是对象proxy,而此处只是对象的值,不是响应式的
     // const {name} = nameObj
     // 使用toRefs包装能够实现响应式
     // 转换:proxy({name: 'mys'}) => {name: proxy({value: 'mys})},此时name是一个proxy对象,是响应式
     const {name} = toRefs(nameObj)
     return {name}


     // 3.readonly限制响应式引用的修改
     // const {reactive, readonly} = Vue
     // const arr = reactive([123])
     // const copyArr = readonly(arr)
     // setTimeout(() => {
     //   arr[0] = 456
     //   copyArr[0] = 456
     // }, 2000)
     // return {arr, copyArr}
   },
 })
 const vm = app.mount('#root')

6-3 toRef 以及 context 参数

  /**
   * toRefs:如果找不到指定数据,不会给一个默认的响应式引用,而是undefined,就不具备响应式
   * toRef: 如果找不到指定数据,就给一个默认为空的响应式数据
   */
  const app = Vue.createApp({
    template: `
      <div>{{ age }}</div>
<!--      <child app="app"/>-->
      <child @change="handleChange">parent</child>
   `,
    methods: {
      handleChange() {
        console.log('handleChange')
      }
    },
    setup(props, context) {
      const {reactive, toRef} = Vue
      const data = reactive({name: 'mys'})
      const age = toRef(data, 'age')
      setTimeout(() => {
        age.value = 'lee'
      }, 2000)
      return {age}
    },
  })

  /**
   * context
   *    attrs:父组件传递过来的 None-props属性
   *    slots:父组件传递来的插槽  类似:this.$slots
   *    emit:触发事件                this.$emit('change')
   */
  app.component('child', {
    template: `
      <div @click="handleClick">child</div>
   `,
    // mounted() {
    //   console.log(this.$slots)
    //   this.$emit('change')
    // },
    setup(props, context) {
      const {h} = Vue
      const {attrs, slots, emit} = context
      // console.log(attrs) // None-props属性
      // return () => h('div', {}, slots.default()) // parent

      function handleClick() {
        emit('change')
      }
      return {
        handleClick
      }
    },
  })
  const vm = app.mount('#root')

6-4 使用 Composition API 开发TodoList

 // 封装list相关的内容
 const listReactiveEffect = () => {
   const {reactive} = Vue
   const list = reactive([])
   const addItemToList = (item) => {
     list.push(item)
   }
   return {list, addItemToList}
 }

 // 封装inputValue的内容
 const inputReactiveEffect = () => {
   const {ref} = Vue
   const inputValue = ref('123')
   const handleInputValueChange = (e) => {
     inputValue.value = e.target.value
   }
   return {inputValue, handleInputValueChange}
 }

 const app = Vue.createApp({
   // 流程调度中转
   setup() {
     const {list, addItemToList} = listReactiveEffect()
     const {inputValue, handleInputValueChange} = inputReactiveEffect()
     return {
       list, addItemToList,
       inputValue, handleInputValueChange
     }
   },
   template: `
     <div>
     <div>
       <input :value="inputValue" @input="handleInputValueChange"/>
       <button @click="() => addItemToList(inputValue)">提交</button>
     </div>
     <ul>
       <li v-for="(item, index) in list" :key="index">{{ item }}</li>
     </ul>
     </div>
`,
 })
 const vm = app.mount('#root')

6-5 computed方法生成计算属性

 /**
  * computed计算属性
  */
 const app = Vue.createApp({
   setup() {
     const {ref, computed} = Vue
     const count = ref(0)
     const handleClick = () => {
       count.value += 1
     }
     // const countAddFive = computed(() => {
     //   return count.value + 5
     // })
     // computed中也能是一个对象
     let countAddFive = computed({
       get: () => {
         return count.value + 5
       },
       set: (param) => {
         count.value = param - 5
       }
     })
     setTimeout(() => {
       countAddFive.value = 100
     }, 3000)
     return {
       count, handleClick, countAddFive
     }
   },
   template: `
     <div>
     <span @click="handleClick">{{ count }}</span> -- {{countAddFive }}
     </div>
`,
 })
 const vm = app.mount('#root')

6-6 watch 和 watchEffect 的使用和差异性

 const app = Vue.createApp({
   setup() {
     const {ref, reactive, watch, toRefs, watchEffect, watchEffectRefs} = Vue
     // const name = ref('mys')
     const nameObj = reactive({
       name: 'mys',
       englishName: 'lee'
     })
     const {name} = toRefs(nameObj)
     /**
      * watch监听器
      * 1.具备一定的惰性 lazy 第一次不执行,发生变化才执行
      * 2.参数currentValue/preValue:当前值和之前值
      * 3.当监听reactive类型的数据时,需要使用箭头函数:() => nameObj.name
      * 4.可以用一个监听器来监听多个数据的变化
      */
     // 1.监听ref类型数据
     // watch(name, (currentValue, preValue) => { }
     // 2.监听reactive类型数据
     // watch(() => nameObj.name, (currentValue, preValue) => {
     //   console.log(currentValue, preValue)
     // })
     // 3.监听多个数据
     watch([() => nameObj.name, () => nameObj.englishName], ([curName, curEng], [preName, preEng]) => {
       console.log('watch', curName, preName, '--', curEng, preEng)
     }, {
       immediate: true // 把watch变成立即执行
     })

     /**
      * watchEffect监听器
      * 1.立即执行,没有惰性 immediate
      * 2.不需要传递监听的内容,会自动感知代码依赖,不需要传递很多参数,只需要传递一个回调函数
      * 3.不能获取之前数据的值
      *
      * 应用:监听Ajax请求;异步请求
      * watch watchEffect都可以通过定时器停止
      */
     const stop = watchEffect(() => {
       console.log('watchEffect', nameObj.name, nameObj.englishName)
       setTimeout(() => {
         stop()
       }, 3000)
     })

     return {name, nameObj}
   },
   template: `
     <div>
     name:<input v-model="nameObj.name "/>
     <div>{{ nameObj.name }}</div>

     englishName:<input v-model="nameObj.englishName "/>
     <div>{{ nameObj.englishName }}</div>
     </div>
`,
 })
 const vm = app.mount('#root')

6-7 生命周期函数的新写法

 const app = Vue.createApp({
   /**
    * 生命周期函数
    * beforeMount => onBeforeMount
    * mounted => onMounted
    * beforeUpdate => onBeforeUpdate
    * beforeUnmounted => onBeforeUnmounted
    * unmounted => onUnmounted
    * 没有beforeCreate/create,直接写在setup中即可
    * 新的生命周期函数:
    * onRenderTracked:每次渲染后重新收集响应式依赖
    * onRenderTriggered:每次触发页面重新渲染时自动执行
    */
   setup() {
     const {
       ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmounted, onUnmounted,
       onRenderTracked, onRenderTriggered
     } = Vue
     const name = ref('mys')
     onBeforeMount(() => {
       console.log('onBeforeMount')
     })
     onMounted(() => {
       console.log('onMounted')
     })
     onBeforeUpdate(() => {
       console.log('onBeforeUpdate')
     })
     onUpdated(() => {
       console.log('onUpdated')
     })
     onRenderTracked(() => {
       console.log('onRenderTracked')
     })
     onRenderTriggered(() => {
       console.log('onRenderTriggered')
     })
     handleClick = () => {
       name.value = 'lee'
     }
     return {name, handleClick}
   },
   template: `
     <div @click="handleClick">
     {{name}}
     </div>
`,
 })
 const vm = app.mount('#root')

6-8 Provide,Inject,模版 Ref 的用法

 /**
  * provide inject
  */
 const app = Vue.createApp({
   setup() {
     const {provide, ref, readonly} = Vue
     const name = ref('mys')
     provide('name', readonly(name)) // 不让子组件修改name
     provide('changeName', (value) => {
       name.value = value
     })
     return {}
   },
   template: `
     <div>
      <child/>
      <domTest/>
     </div>
`,
 })

 app.component('child', {
   setup() {
     const {inject} = Vue
     // 获取父组件传来的name,如果没有,默认值为hello
     const name = inject('name', 'hello')
     const changeName = inject('changeName')
     const handleClick = () => {
       // 如果子组件想要修改数据,需要告诉父组件,让父组件来修改 => 单向数据流
       // 下面的方法虽然可以修改,但是不推荐使用
       // name.value = 'lee'
       changeName('lee')
     }
     return {name, handleClick}
   },
   template: `
     <div @click="handleClick">{{ name }}</div>
`
 })

 /**
  * Composition API语法下  获取真实的DOM元素节点
  * 1.在template中定义一个ref引用 ref="hello"
  * 2.在setup中定义一个与ref名字相同的常量并且等于空 const hello = ref(null)
  * 3.在生命周期函数onMounted中获取dom元素
  * 4.return常量 return {hello}
  */
 app.component('domTest', {
   setup() {
     const {ref, onMounted,} = Vue
     const hello = ref(null)
     onMounted(() => {
       console.log(hello.value)
     })
     return {hello}
   },
   template:`
    <div>
       <div ref="hello">hello world</div>
    </div>
   `
 })
 const vm = app.mount('#root')

7 Vue 项目开发配套工具讲解

7-1 VueCLI 的使用和单文件组件

1、安装node npm nrm

(1)Node环境安装

下载地址:https://nodejs.org/zh-cn/ (下载长期支持版本LTS)

安装完成,在终端输入:node -v 查看版本号

(2)NPM:Node Package Manager

查看版本:npm -v

(3)NRM(Npm Registry Manager )是npm的镜像源管理工具源切换,找到当前最快的安装源**

安装:npm install -g nrm

NRM安装不成功

如果安装nrm出现下面信息,安装失败,解决办法参考:https://blog.csdn.net/blue_698/article/details/117874021

在这里插入图片描述

  1. 执行:npm config list -l,出现下面结果

在这里插入图片描述

  1. 执行npm cache clean --force清除缓存

  2. 执行nrm ls出现下面结果即解决

在这里插入图片描述

NRM常用命令

测试源的速度:nrm test npm

切换源:nrm use taobao

查看可用源:nrm ls

查看当前源:nrm current

增加定制源:nrm add imooc http://192.168.1.100:6666

删除源:nrm del imooc

2、删除老版本工具

如果之前使用过vue,可以通过下面两个命令删除:

npm uninstall vue-cli -g
yarn global remove vue-cli

3、安装vue新版本工具

可以快速搭建vue工程的一个工具:npm install -g @vue/cli,注意:用管理员身份打开终端

4、创建项目

在这里插入图片描述

5、也可以通过IDE来打开项目并启动,这里我用的是webstorm

在这里插入图片描述

6、项目结构说明

在这里插入图片描述

main.js

import { createApp } from 'vue' // 创建Vue的实例 app
import App from './App.vue' // app来自当前文件夹下的App.vue

createApp(App).mount('#app')

App.vue

<!--
  单文件组件:App.vue文件代表一个组件
    1.template 模板
    2.script 核心逻辑
    3.style 样式
-->
<template>
  <img alt="Vue logo" src="./assets/logo.png">
  <HelloWorld msg="Welcome to Your Vue.js App"/>
</template>

<script>
import HelloWorld from './components/HelloWorld.vue'

export default {
  name: 'App', // 根组件
  components: { // 局部组件
    HelloWorld
  }
}
</script>

<style>

</style>

components/HelloWorld.vue

<template>
  <h1>{{ msg }}</h1>
</template>

<script>
export default {
  name: 'HelloWorld',
  props: {
    msg: String
  }
}
</script>

<style>

</style>

7-2 使用单文件组件编写 TodoList

1、在components下写ListItem.vue子组件

<template>
  <li>{{ msg }}</li>
</template>

<script>
export default {
  name: "ListItem",
  props: {
    msg: String
  }
}
</script>

<style scoped>

</style>

2、在单文件组件App.vue中写:template script style

<template>
  <div>
    <input v-model="inputValue"/>
    <button class="button" @click="handleAddItem">提交</button>
  </div>
  <ul>
    <list-item
        v-for="(item, index) in list"
        :key="index"
        :msg="item"
    />
  </ul>
</template>

<script>
import {reactive, ref} from 'vue'
// 引入ListItem组件
import ListItem from './components/ListItem';

export default {
  name: 'App', // 根组件
  // 注册ListItem组件
  components: {ListItem},
  setup() {
    const inputValue = ref('')
    const list = reactive([])
    const handleAddItem = () => {
      list.push(inputValue.value)
      inputValue.value = ''
    }
    return {inputValue, list, handleAddItem}
  },
}
</script>

<style>
.button {
  margin-left: 20px;
}
</style>

7-3 Vue-Router 路由的理解和使用

1、创建项目

步骤上面类似,需要修改:

在这里插入图片描述

2、哈希路由:根据不同的URL展示不同的效果

在这里插入图片描述

3. 流程

1.通过入口main.js,引入router路由 
2.在router文件夹下定义路由 	=>将不同的组件与不同的路径相对应
3.在views下写各个路由所对应的组件内容
4.在App.vue中引入路由跳转及其组件内容
5.通过app实例运行项目

main.js

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

// 哈希路由:根据URL的不同,展示不同的效果
createApp(App).use(router).mount('#app')

router/index.js

import { createRouter, createWebHashHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'

const routes = [
  // 访问不同的路径,展示的组件不同
  {
    path: '/',
    name: 'home',
    component: HomeView
  },
  {
    path: '/about',
    name: 'about',
    // 异步加载路由:访问的时候再去加载
    // 缺点:可能会有卡顿  具体怎么选择根据项目来决定
    component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
  },
  {
    path: '/login',
    name: 'login',
    component: () => import(/* webpackChunkName: "about" */ '../views/LoginView.vue')
  }
]

const router = createRouter({
  history: createWebHashHistory(),
  routes
})

export default router

views/LoginView.vue

<template>
  <div class="login">
    <h1>LoginPage</h1>
  </div>
</template>

App.vue

<template>
  <nav>
    <!-- router-link:跳转路由的标签 -->
    <router-link to="/">Home</router-link> |
    <router-link to="/about">About</router-link> |
    <router-link to="/login">Login</router-link>
  </nav>
  <!-- router-view:展示当前路由对应的组件内容 -->
  <router-view/>
</template>

<style>

</style>

7-4 VueX 的语法详解

1、创建项目
在这里插入图片描述

2、流程

(1)获取数据

1.通过main.js引入router、store
2.在store文件夹下创建数据仓库,用来存放全局数据,在state中定义数据
3.在views中通过computed计算属性来获取数据并展示在页面上
4.通过app实例运行项目

(2)修改数据

1.通过dispatch方法,派发一个action,名为change
2.感知到change的action,执行store中的actions下面的change方法
3.commit提交一个名为change的数据改变
4.mutation感知到提交的change改变,执行change方法,改变数据

dispatch 和 actions关联
commit 和 mutation关联

main.js

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

createApp(App).use(store).use(router).mount('#app')

store/index.js

import { createStore } from 'vuex'

// VueX:数据管理框架
// Vue 创建了一个全局唯一的仓库,用来存放全局的数据
export default createStore({
  state: {
    name: 'mys'
  },
  getters: {
  },
  // 潜移默化的约定:mutations中只能写同步的代码,不能写异步的。异步代码通常放到actions中
  mutations: {
    // 第四步:mutation被执行
    change(state, str) {
      // 第五步:在mutation里面修改数据
      state.name = str
    }
  },
  actions: {
    // 第二步:store感知到触发了一个change的action,执行change方法
    change(store, str) {
      // 第三步:提交一个commit,触发一个mutation
      setTimeout(() => {
        this.commit('change', str)
      }, 2000)
    }
  },
  modules: {
  }
})

views/AboutView.vue

<template>
  <div class="about">
    <h1 @click="handleClick">This is an about page</h1>
    <h1>{{ myName }}</h1>
  </div>
</template>

<script>
export default {
  name: 'AboutView',
  computed: {
    myName() {
      return this.$store.state.name
    }
  },
  methods: {
    /**
     * 改变store中的数据步骤:
     * 1.通过dispatch方法,派发一个action,名为change
     * 2.感知到change的action,执行store中的actions下面的change方法
     * 3.commit提交一个名为change的数据改变
     * 4.mutation感知到提交的change改变,执行change方法,改变数据
     */
    handleClick() {
      // 第一步:想要改变数据,vue要求第一步必须派发一个action
      // change:名字 hello world:参数
      this.$store.dispatch('change', 'hello world')
    }
  },
}
</script>

App.vue

<template>
  <nav>
    <router-link to="/">Home</router-link> |
    <router-link to="/about">About</router-link>
  </nav>
  <router-view/>
</template>

<style>

</style>

7-5 CompositionAPI 中如何使用 VueX

不同之处:在setup()中通过VueX提供的useStore获取全局数据对象
views/AboutView.vue

<template>
  <div class="about">
    <h1 @click="handleClick">This is an about page</h1>
    <h1>{{ name }}</h1>
  </div>
</template>

<script>
import {useStore} from 'vuex'
import {toRefs} from 'vue'

export default {
  name: 'AboutView',
  setup() {
    // 通过useStore获取全局数据对象
    const store = useStore()
    const {name} = toRefs(store.state)
    const handleClick = () => {
      store.dispatch('change', 'hello')
    }
    return {
      name, handleClick
    }
  }
}
</script>

7-6 使用 axios 发送ajax 请求

1、安装axios工具

npm install axios --save

2、在store/index.js发请求

actions: {
    change(store) {
      axios.get('https://www.fastmock.site/mock/ae8e9031947a302fed5f92425995aa19/jd/api/user/register')
        .then((response) => {
          const msg = response.data.message
          console.log(msg)
          store.commit('change', msg)
        })
    }
  },

8 “京东到家”项目首页开发

8-1 工程初始化

在这里插入图片描述

8-2 工程目录代码简介及整理

8-3 基础样式集成及开发模拟器的使用

1、安装normalize.css文件

npm install normalize.css --save

2、src下新建style文件夹,创建base.css

base.css

/* rem与px转换:1rem = 1 html font-size = 100px */
html {
    font-size: 100px;
}

3、在main.js中引入

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import 'normalize.css'
import './style/base.css'

createApp(App).use(store).use(router).mount('#app')

4、将浏览器调整为移动端模式

未完…

Logo

前往低代码交流专区

更多推荐