Vue3.x开发文档

Vue3.x开发文档

1 特点

渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合

  • 易用
    已经知道HTML/CSS/JavaScript即可使用。
  • 灵活
    可以在一个库和一个完整框架之间自由使用。
  • 高效
    运行大小20KB,超快虚拟DOM,最省心的优化

2 Vue3.x VS Vue2.x 主要改进

2.1 提升运行时性能:重写 VDOM

  • VDOM【Virtual DOM】
    • 虚拟DOM元素,视图层框架最重要的就是尽量减少DOM操作的开销,优化dom操作,提示性能。
  • Vue3.x 对比 Vue2.x的优化效果。
    • 性能提升 30% ~ 300%。
  • 实现的核心
    • 跳过静态节点,只处理动态节点,编译的工作量大大减少。【静态标记】

2.2 提升网络性能: tree-shaking 机制

  • tree的介绍
    • 依赖树,所有的依赖都是tree的节点。
  • 怎么shaking的?
    • 通过代码反向检测哪些特性被用到,把用到的打包,其余均不打包。
  • 达到的结果
    • 只打包必要的依赖项,最终打包的体积减少,优化性能。

2.3 完全的 typeScript 支持

  • 强大的类型系统,类型提示,泛型,接口。
    • 大型应用开发必备,bug率减少20%.
  • Vue3对TS完美支持。
    • 完全使用typeScript重写
  • 开发工具支持
    • 主流开发工具TS支持非常友好。

2.4 对用户的便利性改进

  • Fragment: 模板更简单,template无需唯一根节点。
  • Teleport: 布局更灵活。
  • Suspense: 强大的异步组件。
  • composition-api: 逻辑重用
    • 代替了mixin

2.5 生态圈改进

  • vue-router@next
  • vuex@next
  • vue-cli-plugin-vue-enxt
  • text-utils@next
  • DevTools vite
  • Vue2.7
    • 融入部分Vue3功能
    • Vue2.x支持18个月,之后以安全性更新为主。

2.6 Vue3.x向下兼容

  • 大部分api都没有变化,完全兼容2.x的写法
  • 主要改进的api和变化如下文档所述
  • 官方推出2.6.12版本,预计还会推出2.7.x版本,当大家平滑过渡
  • 官网有完善的2.x到3.x的迁徙文档。

3 安装

3.1 标准安装

  • 全局安装新版本脚手架 Vue CLi v4.5 以上
    yarn global add @vue/cli
    # OR
    npm i -g @vue/cli
    # 检测当前版本号
    vue --version   // @vue/cli 4.5.6
    
  • 使用命令创建项目目录
    vue create 项目名   // 选择 3.x 版本 其余选项和2.x一致
    
  • 进入项目目录,再项目中运行
    vue upgrade --next // 所有相关插件更新到最新版本
    
  • 启动开发服务器 或 打包
    # 启动开发服务器
    yarn serve
    # 打包
    yarn build
    

3.2 vite

​ vue是一个web开发构建工具,基于原生ES Module的能力加载模块,速度非常之快【大型项目webpack构建速度慢,有时候改一个东西要等好几秒,vite实现秒开】

  • 使用npm
    # 创建项目
    npm init vite-app 项目名 
    # 进入项目 安装依赖
    npm i
    # 启动项目
    npm run dev
    
  • 使用yarn
    # 创建项目
    yarn create vite-app 项目名
    # 进入项目 安装依赖
    yarn
    # 启动项目
    yarn dev
    

3.3 项目目录详解

  • 一级目录详解
    • node_modules // 依赖模块
    • public // 静态服务器
    • src // 开发源目录
    • .gitignore // git忽略列表
    • babel.config.js // babel配置
    • package.json // 包描述
    • yarn.lock // 详细包信息 Yarn需要锁定整个依赖关系树中所有包的版本的所有内容
  • 二级开发目录 src 详解
    • assets // 静态资源
    • components // 组件 一般是基础组件 或 通用的业务组件
    • App.vue // 根组件
    • main.js // 入口文件

3.4 初始代码详解

# main.js  
import { createApp } from 'vue' // 引入vue创建整个应用对象的方法 createApp
import App from './App.vue' // 引入根组件

createApp(App).mount('#app') // 把根组件传入 创建应用对象 挂载public/index.html 中id为app的div容器
// App.vue
# 和2.x对比 template可以写多个根节点 没有一个根节点的限制 代码提示工具还没有更新
<template>
    <h1>欢迎使用Vue3.x</h1>
    <div>我是内容</div>
</template>

# JS代码
<script>
  export default {
  }
</script>

# CSS样式
<style lang="less" scoped>

</style>

4 setup

setup 函数是一个新的组件选项。作为在组件内使用 Composition API 【组合式API】的入口点。

setup 选项的写法是一个函数,和 data 选项相同。

4.1 调用时机

创建组件实例,然后初始化 props,紧接着就好调用 setup 函数。从生命周期钩子函数的视角来看,它会在beforeCreate 之前就被调用。

export default {
  name: "App",
  setup() {
    console.log("1.执行了setup..."); // 执行时机比较早 在beforeCreate之前
  },
  beforeCreate() {
    console.log("2.执行beforeCreate..");
  },
  created() {
    console.log("3.执行created..", this.a);
  },
};

4.2 模板中使用

如果 setup 返回一个对象,则对象的属性会被组件模板的渲染上下文,可以直接在模板中使用。

<template>
  <div>{{ count }} {{ obj.name }}</div>
</template>

<script>
  import { ref, reactive } from 'vue'

  export default {
    setup() {
      const count = ref(0)
      const obj = reactive({ name: '小貂蝉' })

      // 暴露给模板
      return {
        count,
        obj,
      }
    },
  }
</script>

注意 setup 返回的ref在模板中会自动解开,可以直接使用值,不需要 .value

4.3 参数

setup 函数接收 props 作为其第一个参数:

export default {
  props: { // 外部传入参数
    name: String,
  },
  setup(props) {
    console.log("接收到的参数:", props.name);
  },
};
</script>

注意 props 对象是响应式的,watchEffectwatch 会观察和响应 props 的更新:

export default {
  props: {
    name: String,
  },
  setup(props) {
    // 当父组件的props传入的参数更新后,子组件props同步更新
    watchEffect(() => {
      console.log("接收到的参数:", props.name);
    });
  },
};
</script>

注意: 不要结构 props 对象,会失去响应性。

export default {
  props: {
    name: String,
  },
  setup({ name }) { // 解构了props props就不再是响应式数据了
    watchEffect(() => {
      console.log("接收到的参数:", name);
    });
  },
};
</script>

第二个参数提供了一个上下文对象,从原来2.x中的 this 选择地暴露一些属性

setup(props, context) {
    console.log(context); // 输出结果如下
  /*
  attrs: { ... }
  emit: {  ... }
  slots: { ... }
  */ 
},

4.4 this的用法

thissetup 中不可用。

setup(props, context) {
    console.log(this); // undefined
}

5 响应式系统api

5.1 reactive

接收一个普通对象然后返回该普通对象的响应式代理。等同于 2.x 的 Vue.observable()

const obj = reactive({ name: '小貂蝉' }) // 该对象数据变为响应式数据

响应式转换是“深层次的”,会影响到对象内部所有嵌套属性。基于ES6的 Proxy 实现,返回的代理对象 不等于 原始对象。

// 之后对对象添加属性 删除属性 都会响应式更新dom
const user = reactive({
  prop: {
    name: "赵子龙",
    age: 19,
  },
  compony: {
    name: "德国",
    departMent: "H5",
  },
});

5.2 ref

接收一个参数值并返回一个响应式且可改变的 ref 对象,ref 对象拥有一个指向内部值的属性 .value

setup() {
    const count = ref(0)  // count可以让一个数字变成响应式的ref对象
    console.log( count.value )  // 0 响应ref对象,内部有一个value值 
    
    function add() {
       count.value++
    }
    return {
        count,  // 返回的count直接使用在template模板中,就是响应式的数据
        add // 点击事件触发add count发生变化 dom自动渲染更新
    }
}

注意

  • 当 ref 响应对象 countsetup中返回时,在 template 中渲染,不需要写 .value

    <template>
    	<div>{{ count }} </div>    
    </template>
    
    <script>
    	export default {
            setup() {
                return {
                    count:ref(0)
                }
            }
        }        
    </script>
    
  • 当 ref 作为一个 reactive 对象的属性被修改或者访问时,也不需要写 .value

    const count = ref(0)
    const state = reactive({
        count
    })
    
    console.log(state.count) // 0
    state.count += 1;
    console.log(state.count) // 1
    
  • 如果将一个新的 ref 赋值给现在的响应对象中的 ref, 响应对象中的 ref 将会被替换掉

    // 注意:如果将一个新的 ref 分配给现有的 ref,将替换旧的 ref
    const count = ref(1);
    console.log("原来的ref->", count.value);
    
    const state = reactive({
      count,
    });
    console.log("state.count->", state.count);
    
    // 使用一个新的ref响应式对象 替换原来旧的ref响应式对象
    const otherCount = ref(2);
    state.count = otherCount;
    
    console.log("替换后的->", state, state.count);
    
    console.log("原来的->", count);
    
  • ref 嵌套在 reactive 响应对象 Object中时,才不需要 .value, 如果从 Array 或者 Map 等原生集合类中访问 ref, 都是需要写 .value 的。

    const arr = reactive([ref(0)]);
    console.log(arr[0].value); // 0 需要写 .value 才能访问
    
    const map = reactive(new Map([["foo", ref(100)]]));
    console.log(map.get("foo").value); // 100 需要写 .value 才能访问
    

5.3 computed

传入一个 getter 函数,返回一个默认不可手动修改的 ref 对象。

const count = ref(1);
const plusOne = computed(() => count.value + 1); // 计算属性

console.log(plusOne.value); // 2
plusOne.value += 1;
console.log(plusOne.value); // 2 默认只能获取 不能修改 修改没有效果

或者传入一个 拥有 getset 的函数对象,创建一个可以手动修改的计算状态

const count = ref(1);
const plusOne = computed({
  get: () => count.value,
  set: (val) => {
    count.value = val;
  },
});
console.log(plusOne.value); // 1 获取值 通过get
plusOne.value = 2;
console.log(plusOne.value); // 2 设置值 通过set

5.4 readonly

传入一个对象(响应式或普通)或 ref,返回一个原始对象的__只读__代理,一个只读代理是“深层的”,对象内部任何嵌套的属性也是只读的。

const original = reactive({ count: 0 });
const copy = readonly(original);

watchEffect(() => {
    // 依赖追踪 [函数立即执行 依赖的数据发生变化 也会重新执行]
    console.log(copy.count); //0 10 立即执行了一次 数据变化后再执行一次
});

original.count += 10;

console.log(copy.count); // 10
copy.count++; // reactivity.esm-bundler.js?a1e9:297 Set operation on key "count" failed: target is readonly.
console.log(copy.count); // 10  copy是一个只读的代理对象 不能修改

5.5 watchEffect

立即执行传入的一个函数,并响应式追踪其依赖,并在其依赖变更时重新运行该函数。

const count = ref(0);
watchEffect(() => console.log(count.value)); // 0 1
setTimeout(() => {
  count.value++; // 依赖的数据count改变 会重新执行 watchEffect中的回调函数
}, 100);

停止侦听

watchEffect 在组件的 setup() 函数或生命周期钩子函数被调用时,侦听器会被链接到该组件的生命周期,并在该组件卸载时自动停止。

在一些情况下,也可以显示调用返回值以停止侦听:

// 停止侦听
const count = ref(0);

const stop = watchEffect(() => {
  console.log("count发生了变化:", count.value);
});
setInterval(() => {
  if (count.value === 5) stop(); // 如果等于5 停止侦听 watchEffect回调函数的执行
  count.value++;
}, 1000);

清除副作用

有时副作用函数会执行一些异步的副作用,这些响应需要在其失效时清除(即完成之前状态已经改变 了)。所以侦听副作用传入的函数可以接收一个 onInvalidate 函数作为参数,用来注册清理失效时候的回调。当一下情况发生时,这个__失效回调__ 会被触发。

  • 副作用函数即将重新执行
  • 侦听器被停止( 如果在 setup 或 生命周期钩子函数中使用了 watchEffect, 则在卸组件时)
 const id = ref(0);
function preformAsyncOperation() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("123456");
    }, 1000);
  });
}

watchEffect((onInvalidate) => {
  const token = preformAsyncOperation(id.value);
  onInvalidate(() => {
    // id 改变时 或 停止侦听时
    // 取消之前的异步操作
    console.log("执行");
  });
});

副作用刷新的时机

Vue的响应式系统会缓存副作用函数,并且异步的刷新它们,这样可以避免同一个tick中多个状态改变导致的不必要的重复调用。在核心的具体实现中,组件的更新函数也是一个被侦听的副作用。当一个用户定义的副作用函数进入队列时,会在所有的组件更新后执行。

<template>
  <div>{{ count }}</div>
</template>

<script>
  export default {
    setup() {
      const count = ref(0)
      watchEffect(() => {
        console.log(count.value)
      })
      return {
        count,
      }
    },
  }
</script>

注意:

  • count 会在初始运行时同步打印出来
  • 更改 count 时,将在组件__更新后__ 执行副作用。
  • 初始化运行是在组件声明周期 mounted 之前执行的。因此,如果你希望在编写副作用函数时访问 DOM( 或 模板ref ), 请在 onMounted 钩子中执行。
onMounted(() => {
    watchEffect(() => {
        // 可以访问DOM 或者 template refs
    })
})

// 如:
onMounted(() => {
  console.log("生命周期挂载后...");
  watchEffect(() => {
    console.log(count.value);
    console.log(document.getElementById("box")); // 可以访问到DOM 放到onMounted外部获取到null
  });
});

如果副作用函数需要同步或在组件更新之前运行,可以传递一个拥有 flush 属性的对象作为选项 ( 选项 post );

// 同步运行
wetchEffect(
	() => {
       /* ... */ 
    },
    {
        flush: 'sync'
    }
)

// 组件更新前执行
watchEffect(
	() => {
        /* ... */
    },
    {
        flush: 'pre'
    }
)

5.6 watch

watch API完全等效于2.x this.$watch ( 以及 watch 中相应的选项 )。 watch 需要侦听特定的数据源,并在回调中执行副作用。默认情况是懒执行的,也就是说仅在侦听的源变更时才会执行回调。

  • 对比 watchEffect, watch允许我们:

    • 懒执行副作用
    • 更明确哪些状态的改变会触发侦听器重新运行副作用
    • 访问侦听状态变化后的值
  • 侦听单个数据源

    侦听器的数据源可以使一个返回getter的函数,也可以说ref:

 // 侦听一个getter
const state = reactive({ count: 0 });
watch(
  () => state.count,
  (count, prevCount) => {
    console.log("count->", count, "preCount->", prevCount); // count-> 1 preCount-> 0
  }
);
state.count++;

// 直接侦听一个ref
const count = ref(10);
watch(count, (count, prevCount) => {
  console.log("count->", count, "preCount->", prevCount); // count-> 11 preCount-> 10
});
count.value++;
  • watcher 也可以侦听多个数据源
watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
    // foo bar 为当前的值 【新的值】
    // prevFoo prevBar 为之前的值 【旧的值】
})

6 生命周期钩子函数

可以直接导入 onXXX 的函数来注册生命周期钩子函数:

import { onMounted, onUpdated, onUnmounted } from 'vue'

onMounted(() => {
  console.log("onMounted");
});

onUpdated(() => {
  console.log("onUpdated");
});

onUnmounted(() => {
  console.log("onUnmounted");
});

这些生命周期钩子函数只能在 setup 期间同步使用。

组件实例上下文也是生命周期钩子函数同步执行期间设置的,因此,在卸载组件时,在生命周期钩子内部同步创建的侦听器和计算状态也将同步删除。

  • 与2.x版本生命周期相对于的API

    • beforeCreate - > setup
    • created - > setup
    • beforeMount -> onBeforeMount
    • mounted - > onMounted
    • beforeUpdate - > onBeforeUpdate
    • updated - > onUpdated
    • beforeDestroy - > onBeforeUnmount
    • destroyed - > onUnmounted
    • errorCaptured - > onErrorCaptured
  • 新增的钩子函数

    除了和2.x生命周期相同的周期之外,组合式API还提供了一下调试钩子函数

    • onRenderTracked
    • onRenderTriggered

    两个钩子函数都接收一个 DebuggerEvent, 与 watchEffect 参数选项中的 onTrackonTrigger 类似

export default {
    onRenderTrigger(e) {
        debugger; // 检查哪个依赖数据导致组件重新渲染
    }
}

7. Teleport

Teleport提供了一种的方法,使我们可以控制要在DOM中哪个父对象下呈现HTML,而不必求助于全局状态或将其拆分为两个部分.

Tepeport可以让我们的组件在别的dom容器中呈现,而组件内部的状态不会发生任何变化。

7.1 teleport传送的目标位置

# index.html
 <div id="app"></div>
    
<!-- 传送目标 -->
<div id="teleport-target"></div>

7.2 App.vue使用模态框组件

<template>
  <div class="app">
    <h1>teleport demo</h1>

	# 使用模态框组件
    <Model></Model>
  </div>

</template>

<script>
  import Model from './components/model.vue' // 引入模态框组件

  export default {
    components: {
      Model // 注册模态框组件
    }
  }
</script>

<style lang="less" scoped>
</style>

7.3 模态框组件

<template>
  <div>
    <button @click="showModel" class="btn">查看详情</button>
	
    # 最终 teleport内部的内容 渲染到index.html中的容器 div#teleport-target中 实现传送
    <!-- to 属性就是最终渲染的目标容器位置】 -->
    <teleport to="#teleport-target">
      <div v-show="visible" class="model">
        <div class="model-wrapper">
          <div class="content">内容</div>
          <button @click="hideModel" >取消</button>
        </div>
      </div>
    </teleport>
  </div>

</template>

<script>
 import { ref } from 'vue'; # 引入ref
  
  export default {
      setup() {
        const visible = ref(false); # 定义响应式数据
        
        const showModel = () => { # 显示模态框
          visible.value = true;
        }

        const hideModel = () => { # 隐藏模态框
          visible.value = false
        }
		
        # 暴露给组件
        return {
          visible, 
          showModel,
          hideModel
        }
      }
    }
</script>

<style lang="less" scoped>
 # 模态框组件样式
.model {
  position: fixed;
  display: flex;
  justify-content: center;
  align-items: center;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0,0,0, 0.5);
  z-index: 10;
  .model-wrapper {
    display: flex;
    flex-direction: column;
    padding: 20px;
    width: 400px;
    height: 300px;
    background-color: #fff;
    border-radius: 4px;
    .content {
      flex:1;
    }
    button {
      height: 30px;
      background-color: #fff;
      border: 1px solid rgba(0,0,0,0.1);
      box-shadow: 2px 2px 2px #ccc;
      outline: none;
    }
  }
}
</style>

8 Fragments

vue3.x中,组件支持一个template中有多个根节点,即Fragments文档片段

8.1 Vue2.x不支持多个根组件

<template>
  <div>
    <header>...</header>
    <main>...</main>
    <footer>...</footer>
  </div>
</template>

8.2 Vue3.x支持多个根组件

<template>
  <header>...</header>
  <main v-bind="$attrs">...</main>
  <footer>...</footer>
</template>

9 provide 和 inject

provide()inject() 可以实现嵌套组件之间的数据传递。这两个函数只能在 setup() 函数中使用。父级组件中使用 provide() 函数向下传递数据;子级组件中使用 inject() 获取上层传递过来的数据,无论层级多少,都可以获取父组件传递过来的数据。

9.1 共享数据

# 根组件App.vue
<template>
  <div id="app">
    <h1>我是根组件 App.vue</h1>
    <my-component1></my-component1>
  </div>
</template>

<script>
import MyComponent1 from './components/my-component1.vue' // 引入组件1

import { provide } from 'vue' // 引入 provide

export default {
  name: 'app',
  setup() {
    provide('initColor', 'red') // 给子组件传递数据
  },
  components: {
    MyComponent1 // 注册组件
  }
}
</script>
# 子组件1 【第一个层级】
<template>
  <div>
    // 使用父组件传递过来的数据
    <h3 :style="{'background-color': color}">组件1</h3>
    <hr />
    <my-component2></my-component2>
  </div>
</template>

<script>
import MyComponent2 from './my-component2.vue' // 引入子组件2

import { inject } from 'vue' // 引入inject

export default {
  setup() {
    const color = inject('initColor') // 获取父组件的数据

    return {
      color
    }
  },
  components: {
    MyComponent2 // 注册组件
  }
}
</script>
# 子组件2
<template>
  <div>
    // 使用父组件传递过来的数据
    <h5 :style="{backgroundColor: color}">组件2</h5>
  </div>
</template>

<script>
// 1. 按需导入 inject
import { inject } from 'vue'

export default {
  setup() {
    const color = inject('initColor') // 获取父组件传递过来的数据
    return {
      color
    }
  }
}
</script>

9.2 共享ref响应式数据

<template>
  <div id="app">
    <h1>App 根组件</h1>
	# 点击按钮 切换不同的颜色
    <button @click="color='red'">红色</button>
    <button @click="color='green'">绿色</button>
    <button @click="color='pink'">粉色</button>

    <hr />
    # 使用组件1
    <my-component1></my-component1>
  </div>
</template>

<script>
# 引入组件1
import MyComponent1 from './components/my-component1.vue'
import { provide, ref } from 'vue' # 引入 provide 和 ref
export default {
  name: 'app',
  setup() {
    const color = ref('red')  # 响应式数据 color

    provide('initColor', color) # 传递数据给子组件

    return {
      color
    }
  },
  # 注册组件
  components: {
    MyComponent1
  }
}
</script>

10 template Refs

10.1 引用template中的元素

<template>
  <div>
    <h3 ref="title">组件1</h3>
  </div>
</template>

<script>
import { ref, onMounted } from 'vue'

export default {
  setup() {
    const title = ref(null) // 设置响应式数据title
    
    // 生命周期 onMounted 可以操作dom
    onMounted(() => {
      title.value.style.color = 'red'
    })
      
    return {
      title
    }
  }
}
</script>

10.2 引用组件

# App.vue 根组件
<template>
  <div>
    <h3>App 根组件</h3>
    <button @click="getNumber">获取组件1中的count的值</button>
    <!-- 组件1 -->
    <my-component1 ref="comP1"></my-component1>
  </div>
</template>

<script>
import { ref } from 'vue'
import MyComponent1 from './components/my-component1'

export default {
  setup() {
    const comP1 = ref(null) # 响应式数据comP1
	
    # 获取数据的方法
    const getNumber = () => {
      console.log(comP1.value.count)
    }

    return {
      comP1,
      getNumber
    }
  },
  components: {
    MyComponent1
  }
}
</script>
# 组件1
<template>
  <div>
    <h5>组件1 {{count}}</h5>
    <button @click="count+=1">+1</button>
  </div>
</template>

<script>
import { ref } from 'vue'

export default {
  setup() {
    const count = ref(0)
    return {
      count
    }
  }
}
</script>
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值