带你走入Vue3.x新特性,了解与Vue2.x的综合利用

资源地址

Vue3.x 与 Vue2.x 中文文档 … … Go

初始化项目

- 安装 vue-cli3 并 创建项目 Vue3
//npx执行命令下,有未安装的会先安装后再执行命令
npx @vue/cli create vue3

Vue3.x

- 项目结构

在这里插入图片描述

新特性使用

之前了解过Vue3的文档都知道,要使用新特性,都需要安装依赖:composition-api

composition-api 文档地址:
https://github.com/vuejs/composition-api/blob/master/README.zh-CN.md

现在(20201023)的官方文档表明:

当迁移到 Vue 3 时,只需简单的将 @vue/composition-api 替换成 vue 即可。你现有的代码几乎无需进行额外的改动。
- 查看差异点击传送门:

差异传送门 … … Go

- setup
  • 该函数是 vue3 中,专门为组件提供的新属性。它为我们使用 vue3 的新特性提供了统一的入口。
    export default {
    	// props 为当前单页面的 props 
    	// context 为替代 this 的,里面包含:
    	//		context.attrs
    	//		context.slots
    	//		context.parent
    	//		context.root
    	//		context.emit
        //		context.refs
        setup(props, context){
            //....代码
            //在 setup() 函数中无法访问到 this
        },
    }
    
  • 函数的执行时机(顺序)
    • 会在 beforeCreate 之后、created 之前执行
- reactive
- 什么是 reactive? -reactive是Vue3中提供的实现响应式数据的方法
-在Vue2中响应式数据是通过defineProperty来实现的 
而在Vue3屮响应式数据是通过ES6的Proxy来实现的
- reactive 注意点:
-reactive参数必须是对象(json/arr〕
-如果给 reactive传递了其它对象
+默认情况下修改对象,界面不会自动更新 
+如果想更新,可以通过重新赋值的方式
- 函数接收一个普通对象,返回一个响应式的数据对象。
- 等价于 vue 2.x 中的 Vue.observable() 函数,vue 3.x 中提供了 reactive() 函数,用来创建响应式的数据对象,基本代码示例如下:
  • import { reactive } from 'vue'
    export default {
        setup(){
            // 创建响应式数据对象
    	    const state = reactive({count: 0})
    	     // setup 函数中将响应式数据对象 return 出去,供 template 使用
    	    return state
        },
    }
    
    <p>当前的 count 值为:{{count}}</p>
    
- ref
- 什么是ref?
-ref和reactive—样,也是用来实现响应式数据的方法 
-由于reactive必须传递一个对象,所以导致在企业开发中 
如果我们只想让某个变垦实现响应式的时候会非常麻烦 
所以Vue3就给我们提供fref方法,实现对简单值的监听
- ref本质:
-ref 底层的本质其实还是reactive
系统会自动根据我们给ref传入的值将它转换成 
ref(xx) -> reactive ({value :xx})
- ref注意点:
-在Vue中使用ref的位不用通过vaLue获取 
-在JS中使用ref的值必须通过value获取
- 函数用来根据给定的值创建一个响应式的数据对象,ref() 函数调用的返回值是一个对象,这个对象上只包含一个 .value 属性:
  • import { ref } from 'vue'
    	export default {
    	    setup(){
    	        // 创建响应式数据对象 count,初始值为 0
    			const count = ref(0)
    			// 如果要访问 ref() 创建出来的响应式数据对象的值,必须通过 .value 属性才可以
    			console.log(count.value) // 输出 0
    			// 让 count 的值 +1
    			count.value++
    			// 再次打印 count 的值
    			console.log(count.value) // 输出 1
    			
    			return {
    		         count
    		     }
    	    },
        }
    
    <p>当前的 count 值为:{{count}}</p>
    
- ref 和reactive区别
如果在template里使用的是ref类型的数据,那么Vue会自动帮我们添加.value 
如果在template里使用的是reactive类型的数据,那么Vue不会自动帮我们添加.value

Vue是如何决定是否需要自动添加.value的
Vue在解析数据之前,会自动判断这个数据是否是ref类型的,
如果是就自动添加.value,如果不是就不自动添加.value

Vue是如何判断当前的数据是否是ref类期的 
通过当前数据的 _ _v_ref 来判断的
如果有这个私有的属性,并且取值为true,那么就代表是一个ref类型的数据
- 在 reactive 对象中访问 ref 创建的响应式数据
- 当把 ref() 创建出来的响应式数据对象,挂载到 reactive() 上时,会自动把响应式数据对象展开为原始的值,不需通过 .value 就可以直接被访问,例如:
  • const count = ref(0)
    const state = reactive({
      count
    })
    
    console.log(state.count) // 输出 0
    state.count++ // 此处不需要通过 .value 就能直接访问原始值
    console.log(count) // 输出 1
    
- 新的 ref 会覆盖旧的 ref,示例代码如下:
  • // 创建 ref 并挂载到 reactive 中
    const c1 = ref(0)
    const state = reactive({
      c1
    })
    
    // 再次创建 ref,命名为 c2
    const c2 = ref(9)
    // 将 旧 ref c1 替换为 新 ref c2
    state.c1 = c2
    state.c1++
    
    console.log(state.c1) // 输出 10
    console.log(c2.value) // 输出 10
    console.log(c1.value) // 输出 0
    
- isRef
- 用来判断某个值是否为 ref() 创建出来的对象;应用场景:当需要展开某个可能为 ref() 创建出来的值的时候,例如:
  • import { isRef } from 'vue'
    const unwrapped = isRef(foo) ? foo.value : foo
    
- toRefs
- 函数可以将 reactive() 创建出来的响应式对象,转换为普通的对象,只不过,这个对象上的每个属性节点,都是 ref() 类型的响应式数据,最常见的应用场景如下:
  • import { toRefs } from 'vue'
    setup() {
        // 定义响应式数据对象
        const state = reactive({
          count: 0
        })
    
        // 定义页面上可用的事件处理函数
        const increment = () => {
          state.count++
        }
    
        // 在 setup 中返回一个对象供页面使用
        // 这个对象中可以包含响应式的数据,也可以包含事件处理函数
        return {
          // 将 state 上的每个属性,都转化为 ref 形式的响应式数据
          ...toRefs(state),
          // 自增的事件处理函数
          increment
        }
    }
    
- 页面上可以直接访问 setup() 中 return 出来的响应式数据:
  • <template>
      <div>
        <p>当前的count值为:{{count}}</p>
        <button @click="increment">+1</button>
      </div>
    </template>
    
- toRaw
- 这是一个获取代理对象的根数据方法,在以下使用中,修改根数据后,不影响代理对象 msg 的数据变动,也就不会导致视图重新渲染,所以可作为是一个用来优化资源加载的方案。
  • //reactive 的 toRaw 用法
    let obj = {content: 'content'}
    let msg = reactive(obj);
    let raw = toRaw(msg);
    obj.content = 'contentEdit'	//修改源数据
    //msg.content = 'contentMsgEdit'	//修改代理数据
    console.log(raw ); //-> {content: 'contentEdit'}
    console.log(msg ); //-> {content: 'content'}
    
    //ref 的 toRaw 用法
    let obj = {content: 'content'}
    let msg = ref(obj);
    let raw = toRaw(msg);
    obj.content = 'contentEdit'	//修改源数据
    //msg.value.content = 'contentMsgEdit'	//修改代理数据
    console.log(raw ); //-> {content: 'contentEdit'}
    console.log(msg ); //-> {content: 'content'}
    
- markRaw
- 显式标记一个 对象 (不能是简单类型) 为“永远不会转为响应式代理”,函数返回这个对象本身。
  • const foo = markRaw({a:'1'})
    console.log(isReactive(reactive(foo))) // false
    // 如果被 markRaw 标记了,即使在响应式对象中作属性,也依然不是响应式的
    const bar = reactive({ foo })
    console.log(isReactive(bar.foo)) // false
    foo.a = 2
    console.log(foo) // -> {a:'2'}
    1 //为视图显示的原值,因无法代理,所以跟踪不到value被变化而更新视图
    
- computed
- 用来创建计算属性,computed() 函数的返回值是一个 ref 的实例。使用 computed 之前需要按需导入:
  • import { computed } from 'vue'
    
- 在调用 computed() 函数期间,传入一个 function 函数,可以得到一个只读的计算属性,示例代码如下:
  • // 创建一个 ref 响应式数据
    const count = ref(1)
    
    // 根据 count 的值,创建一个响应式的计算属性 plusOne
    // 它会根据依赖的 ref 自动计算并返回一个新的 ref
    const plusOne = computed(() => count.value + 1)
    
    console.log(plusOne.value) // 输出 2
    plusOne.value++ // error
    
- 在调用 computed() 函数期间,传入一个包含 get 和 set 函数的对象,可以得到一个可读可写的计算属性,示例代码如下:
  • // 创建一个 ref 响应式数据
    const count = ref(1)
    // 创建一个 computed 计算属性
    const plusOne = computed({
      // 取值函数
      get: () => count.value + 1,
      // 赋值函数
      set: val => {
        count.value = val - 1
      }
    })
    // 为计算属性赋值的操作,会触发 set 函数
    plusOne.value = 9
    // 触发 set 函数后,count 的值会被更新
    console.log(count.value) // 输出 8
    
- watch
  • 函数用来监视某些数据项的变化,从而触发某些特定的操作,使用之前需要按需导入:
    • import { watch } from 'vue'
- 基本用法
  • const count = ref(0)
    
    // 定义 watch,只要 count 值变化,就会触发 watch 回调
    // watch 会在创建时会自动调用一次
    watch(() => console.log(count.value))
    // 输出 0
    
    setTimeout(() => {
      count.value++
      // 输出 1
    }, 1000)
    
- 监视 reactive 类型的数据源:
  • // 定义数据源
    const state = reactive({ count: 0 })
    // 监视 state.count 这个数据节点的变化
    watch(
      () => state.count,
      (count, prevCount) => {
        /* ... */
      }
    )
    //--------------------------------
    //监视多个
    const state = reactive({ count: 0, name: 'zs' })
    watch(
      [() => state.count, () => state.name], // Object.values(toRefs(state)),
      ([count, name], [prevCount, prevName]) => {
        console.log(count) // 新的 count 值
        console.log(name) // 新的 name 值
        console.log('------------')
        console.log(prevCount) // 旧的 count 值
        console.log(prevName) // 新的 name 值
      },
      {
        lazy: true // 在 watch 被创建的时候,不执行回调函数中的代码
      }
    )
    setTimeout(() => {
      state.count++
      state.name = 'ls'
    }, 1000)
    
- 监视 ref 类型的数据源:
  • // 定义数据源
    const count = ref(0)
    // 指定要监视的数据源
    watch(count, (count, prevCount) => {
      /* ... */
    })
    //--------------------------------
    //监视多个
    const count = ref(0)
    const name = ref('zs')
    watch(
      [count, name], // 需要被监视的多个 ref 数据源
      ([count, name], [prevCount, prevName]) => {
        console.log(count)
        console.log(name)
        console.log('-------------')
        console.log(prevCount)
        console.log(prevName)
      },
      {
        lazy: true
      }
    )
    setTimeout(() => {
      count.value++
      name.value = 'xiaomaolv'
    }, 1000)
    
- 在 setup() 函数内创建的 watch 监视,会在当前组件被销毁的时候自动停止。如果想要明确地停止某个监视,可以调用 watch() 函数的返回值即可,语法如下:
  • // 创建监视,并得到 停止函数
    const stop = watch(() => {
      /* ... */
    })
    // 调用停止函数,清除对应的监视
    stop()
    
- 清除创建的异步 watch 监视
  • // 定义响应式数据 keywords
    const keywords = ref('')
    
    // 异步任务:打印用户输入的关键词
    const asyncPrint = val => {
      // 延时 1 秒后打印
      return setTimeout(() => {
        console.log(val)
      }, 1000)
    }
    
    // 定义 watch 监听
    watch(
      keywords,
      (keywords, prevKeywords, onCleanup) => {
        // 执行异步任务,并得到关闭异步任务的 timerId
        const timerId = asyncPrint(keywords)
    
        // 如果 watch 监听被重复执行了,则会先清除上次未完成的异步任务
        onCleanup(() => clearTimeout(timerId))
      },
      // watch 刚被创建的时候不执行
      { lazy: true }
    )
    
    // 把 template 中需要的数据 return 出去
    return {
      keywords
    }
    
- watchEffect
- 作用是在响应式数据发生变化的时候产生对应的操作
  • let obj = reactive({foo: 1}); // obj 是响应式数据
    watchEffect(() => {console.log(obj.foo);}) // 跟踪依赖数据 (obj.foo)
    function inc() {obj.foo += 1;}
    inc()	// -> 2
    inc()	// -> 3
    inc()	// -> 4
    
- 值得注意的是,如果跟踪的依赖数据不会向下延伸
  • let obj = reactive({foo: 1}); // obj 是响应式数据
    let trackObj = {foo: obj.foo};
    watchEffect(() => {
    	console.log(obj.foo);
    	console.log(trackObj.foo.foo);
    }) // 跟踪依赖数据 (obj.foo)
    function inc() {obj.foo += 1;}
    inc()	// -> 2  1
    inc()	// -> 3  1
    inc()	// -> 4	 1
    
- customRef
- 该函数适用于自定义 ref (所谓的被追踪对象)
  • 有的 ref 可以与视图层实现双向数据绑定,而有的则不能。
    假如我们需要自定义一个 ref ,当这个 ref 监听的数据变化时,执行我们自己定义的方法,
    就像是 watchfEffect 一样去检测一个数据,则可以使用 customRef
    
- 作用于普通数据的实际用法一:
  • // customRef用于 自定义ref
    // 自定义 ref 需要提供参数传值
    function myRef(value) {
        // 自定义 ref 需要提供 customerRef 返回值
        // customer 需要提供一个函数作为参数
        // 该函数默认带参数 track 和 trigger ,都是方法。
        // track 告诉 Vue 这个对象需要跟踪
        // trigger 告诉 Vue 这个对象更新了,你可以更新视图了
        return customRef((track, trigger) => {
          return {
            // customer 需要提供一个对象 作为返回值
            // 该对象需要包含 get 和 set 方法。
            get() {
              // track 方法放在 get 中,用于提示这个数据是需要追踪变化的
              track();
              console.log('get', value);
              // get 方法需要返回值,一般就是 value,当然也可以自定义。
              return value;
            },
            // set 传入一个值作为新值,通常用于取代 value
            set(newValue) {
              console.log('set', newValue);
              value = newValue;
              // 如果需要追踪,记得触发事件 trigger
              trigger();
            }
          }
        })
    }
    
- 作用于通过异步获取的后台数据实际用法二:
  • function fetchRef(value) {
      return customRef((track, trigger) => {
        // 用于存储获得的数据
        let ans;
        function getAns() {
          fetch(value)
            .then((res) => {
              return res.json();
            }).then((data) => {
              console.log(data);
              // 将获得的数据存储起来
              ans = data;
              // 提示触发视图层变化
              trigger();
            }).catch((err) => {
              console.log(err);
            });
        }
        getAns();
        return {
          get() {
            track();
            return ans;
          },
          set(newValue) {
            value = newValue;
            // 修改 value 的同时再次进行数据的抓取
            getAns();
          }
        }
      })
    }
    

新版生命周期使用

- 新版的生命周期函数,可以按需导入到组件中,且只能在 setup() 函数中使用,代码示例如下:
import { onMounted, onUpdated, onUnmounted } from 'vue'
const MyComponent = {
  setup() {
    onMounted(() => {
      console.log('mounted!')
    })
    onUpdated(() => {
      console.log('updated!')
    })
    onUnmounted(() => {
      console.log('unmounted!')
    })
  }
}
- 下面的列表,是 vue 2.x 的生命周期函数与新版 Composition API 之间的映射关系:
- ~~beforeCreate~~  -> setup()
- ~~created~~  ->  setup()
- beforeMount -> onBeforeMount
- mounted -> onMounted
- beforeUpdate -> onBeforeUpdate
- updated -> onUpdated
- beforeDestroy -> onBeforeUnmount
- destroyed -> onUnmounted
- errorCaptured -> onErrorCaptured

新增的 provide & inject

- provide() 和 inject() 可以实现嵌套组件之间的数据传递。这两个函数只能在 setup() 函数中使用。父级组件中使用 provide() 函数向下传递数据;子级组件中使用 inject() 获取上层传递过来的数据。
  • //父组件
    import { provide } from 'vue'
    //以下代码必须在:setup()内执行
    provide('navData',data); 
    
    //子组件
    import { inject} from 'vue'
    //以下代码必须在:setup()内执行
    const data = inject('navData');
    console.log(data); 
    

单文件中 refs 的使用

- 通过 ref() 还可以引用页面上的元素或组件。
  • <template>
      <div>
        <h3 ref="h3Ref">TemplateRefOne</h3>
      </div>
    </template>
    
    import { ref, onMounted } from 'vue'
    //以下代码必须在:setup()内执行
    // 创建一个 DOM 引用
    const h3Ref = ref(null)
    // 在 DOM 首次加载完毕之后,才能获取到元素的引用
    onMounted(() => {
      // 为 dom 元素设置字体颜色
      // h3Ref.value 是原生DOM对象
      h3Ref.value.style.color = 'red'
    })
    // 把创建的引用 return 出去
    return {
      h3Ref
    }
    
- 父子组件使用
- 父组件
  • <template>
      <div>
        <h3>父组件</h3>
        <!-- 4. 点击按钮展示子组件的 count 值 -->
        <button @click="showNumber">获取TemplateRefTwo中的count值</button>
        <hr />
        <!-- 3. 为组件添加 ref 引用 -->
        <TemplateRefTwo ref="comRef" />
      </div>
    </template>
    
    // 1. 创建一个组件的 ref 引用
        const comRef = ref(null)
    
        // 5. 展示子组件中 count 的值
        const showNumber = () => {
          console.log(comRef.value.count)
        }
    
        // 2. 把创建的引用 return 出去
        return {
          comRef,
          showNumber
        }
    
- 子组件
  • <template>
      <div>
        <h5>子组件 --- {{count}}</h5>
        <!-- 3. 点击按钮,让 count 值自增 +1 -->
        <button @click="count+=1">+1</button>
      </div>
    </template>
    
    // 1. 定义响应式的数据
    const count = ref(0)
    
    // 2. 把响应式数据 return 给 Template 使用
    return {
      count
    }
    

createComponent

- 这个函数不是必须的,除非你想要完美结合 TypeScript 提供的类型推断来进行项目的开发。
  • 这个函数仅仅提供了类型推断,方便在结合 TypeScript 书写代码时,能为 setup() 中的 props 提供完整的类型推断。
    
    import { createComponent } from 'vue'
    
    export default createComponent({
      props: {
        foo: String
      },
      setup(props) {
        props.foo // <- type: string
      }
    })
    

递归监听与非递归监听

- 要点
  • 1. 递归监听存在的问题
    	如果数据量比较大,非常消耗性能
    2. 非递归监听
    	shallowRef / shallowReactive
    3. 如何触发非递归监听属性更新界面?
    	如果是shallowRef类型数据,可以通过triggerRef来触发
    4. 应用场景
    	—般情况下我们使用ref和reactive即可
    	只有在需要监听的数据量比较大的时候,我们才使用shallowRef/shallowReactive
    
- 使用 shallowRef 或 shallowReactive 监听的数据结构
  • let obj = {
    	a:'1',
    	b:{
    		b1:'2',
    		b2:{
    			b21:'3',
    			b22:{ b223:'4'}
    		}
    	}
    }
    
- shallowReactive 的监听
  • obj.a = 9  
    obj.b = {b1:'10',b2:{b21:'11',b22:{b223:'12'}}}
    //如果以上第一层不改动,那么一下改的值将不被监听并且数据还是之前的数据
    obj.b.b1 = 10
    obj.b.b2.b21 = 11
    obj.b.b2.b22.b223 = 12
    
- shallowRef 的监听
  • //因为 ref 对象自动添加一个 key 为value,导致监听对象为:value:obj
    obj.value = {a:'1',b:{b1:'10',b2:{b21:'11',b22:{b223:'12'}}}}
    //如果以上第一层不改动,那么一下改的值将不被监听并且数据还是之前的数据
    obj.value.b.b1 = 10
    obj.value.b.b2.b21 = 11
    obj.value.b.b2.b22.b223 = 12
    //但是官网给了 shallowRef 监听一个 triggerRef 方法让非遍历监听对象响应其他层级的数据修改
    //使用如下:
    obj.value.b.b2.b21 = 11
    triggerRef(obj)
    

Vue3与Vue2 的核心

Vue3与Vue2的响应式数据本质
Vue3 的Proxy 使用
let obj = {name:'Lnj1', age:18}; 
let state = new Proxy(obj, i
	get(obj, key){
		console.log(obj, key); 
		return obj[key];
	},
	set(obj, key, value){
		console.log(obj, key, value);
		obj[key] = value; 
		console.log('你设置新值了');
		return true
	}
}
手写 shallowRef 和 shallowReactive
function shallowRef(val) {
	return shallowReactive( obj: {value:val});
function shallowReactive(obj) { 
	return new Proxy(obj, handler: {
	get(obj, key){
		return obj[key];
	},
	set(obj, key, val){ 
		obj[key] = val;  
		return true;
	})
}
手写 ref 和 reactive
function ref(val) {
	return reactive( obj: {value:val});
function reactive(obj) { 
	if(typeof obj === 'object'){ 
		if(obj instanceof Array){
			//如果是一个数组,那么取出数组中的毎一个元素,
			//判断毎个一个对象是否是个对象,如果不是个对象,那么也需要包装成 Proxy 
			obj.forEach((item, index) =>{
				if(typeof item === 'object'){ 
					obj[index] = reactive(item);
				}
			})
		}else{
		//如果是一个对象,那么取出对象属性的取值,
		//判断对象属性的取值是否又是一个对象,如果不是一个对象,那么也需要包装成Proxy 
			for(let key in obj){ 
				let item = obj[key]; 
				if(typeof item === 'object'){
					obj[key] = reactive(item);
				}
			}
		}
		return new Proxy(obj, handler: {
		get(obj, key){
			return obj[key];
		},
		set(obj, key, val){ 
			obj[key] = val;  
			return true;
		})
	}else{
		console.log('所传的必须为对象')
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值