3 Star 8 Fork 2

destiny001 / vue3.0

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
README.md 18.58 KB
一键复制 编辑 原始数据 按行查看 历史
destiny001 提交于 2021-07-26 15:12 . vue3-组件通信

vue3.0

01-vue3现状

vue-next 2020年09月18日,正式发布vue3.0版本。但是由于刚发布周边生态不支持,大多数开发者处于观望。

Vue3优点:

  • 最火框架,它是国内最火的前端框架之一,官方文档 中文文档
  • 性能提升,运行速度事vue2.x的1.5倍左右
  • 体积更小,按需编译体积比vue2.x要更小
  • 类型推断,更好的支持Ts(typescript)这个也是趋势
  • ★组合API (composition api) ,能够更好的组织逻辑,封装逻辑,复用逻辑

现在公司90%还是vue2.0,新的项目可能会采用3.0

02-vite

vite介绍

vite是什么:官方文档

  • 它是一个更加轻量(热更新速度快,打包构建速度快)的vue项目脚手架工具。
  • 相对于vue-cli它默认安装的插件非常少,随着开发过程依赖增多,需要自己额外配置。
  • 所以: 在学习vue3语法或者做小型项目会使用它,做大型项目的时候我们还是使用vue-cli

vite基本使用:

  • 创建项目 npm init vite-app 项目名称 或者 yarn create vite-app 项目名称
  • 安装依赖 npm i 或者 yarn
  • 启动项目 npm run dev 或者 yarn dev

vite和webpack的区别

webpack打包过程

  • 识别入口文件
  • 通过逐层识别模块依赖
  • webpack做的就是分析代码。转换代码,编译代码,输出代码
  • 最终形成打包后的代码

vite原理

  • 当声明一个 script 标签类型为 module 时
 <script type="module" src="/src/main.js"></script>
//浏览器就会像服务器发起一个GET http://localhost:3000/src/main.js请求main.js文件:
// /src/main.js:
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
  • 浏览器请求到了main.js文件,检测到内部含有import引入的包,又会对其内部的 import 引用发起 HTTP 请求获取模块的内容文件
  • 如:GET http://localhost:3000/@modules/vue.js
  • 如:GET http://localhost:3000/src/App.vue
  • Vite 的主要功能就是通过劫持浏览器的这些请求,并在后端进行相应的处理将项目中使用的文件通过简单的分解与整合,然后再返回给浏览器,vite整个过程中没有对文件进行打包编译,所以其运行速度比原始的webpack开发编译速度快出许多!

03-项目创建

  1. 全局安装yarn

    npm init vite-app vue3-demo(项目名)
  2. 进入到项目

    cd vue3-demo
  3. 装包

    npm i/ yarn
  4. 运行

    npm run dev
  5. main.js 入口代码

    import { createApp } from 'vue'
    import App from './App.vue'
    import './index.css'
    
    // 创建vue应用并渲染App组件,挂载到#app容器中
    createApp(App).mount('#app')

04-vue3.0 组合式api

vue2.0采用的叫做选项式api:

例如我们想实现某一个功能,关于这个功能的数据我们会定义在data中,事件函数定义在methods中,计算属性定义在computed中...

new Vue({
  data () {
    return {
      a: 213,
      b: 2,
      c: 5,
      d: 6
    }
  },
  methods: {
    add () {},
    remove () {}
  },
  computed: {
    totalPrice () {}
  }
})

实际上我们想实现一个功能会把我们代码拆分写到不同的模块中,这样我们很难区分,哪个数据和哪个逻辑是一一对应的,而且想实现这个功能也要在data、methods、computed中跳来跳去。

Vue3.0的组合式(composition-api)api:

我们会把同一功能的逻辑写在一起

<script>
export default {
  name: 'App',
  setup() {
    // 添加功能
    const a = 1
    const add = () => {}
    
    // 删除功能 
    const b = 2
    const remove = () => {}
  }
}
</script>

05-setup

基本使用

  1. setup是composition-api的入口函数

  2. 在生命周期beforeCreate之前被调用

  3. setup内部不能使用this获取到组件实例

  4. setup内部可以返回数据和方法供模版使用

    <template>
      <h1>{{ count }}</h1>
      <button @click="clickHandle">点击事件</button>
    </template>
    
    <script>
    
    export default {
      name: 'App',
      beforeCreate () {
        console.log(123)
      },
      setup() {
        const count = 10
    
        const clickHandle = () => {
          console.log('点我了')
        }
        return {
          count,
          clickHandle
        }
      }
    }
    </script>

⚠️:setup内部直接定义的数据不是响应式的

定义响应式数据

reactive

可以将一个复杂数据类型代理为响应式数据

  1. 引入reactive

    import { reactive } from 'vue'
  2. 通过reactive定义响应式数据

    setup() {
      // 定义响应式数据
      const data = reactive({
        count: 10
      })
    
      const clickHandle = () => {
        // 修改数据
        data.count++
      }
      return {
        data,
        clickHandle
      }
    }
  3. 模版中使用响应式数据

    {{ data.count }}

toRef

将响应式对象中某一个属性结构成响应式数据

直接结构无法达成响应式

<template>
<h1>用户:{{ name }}</h1>
<button @click="clickHandle">修改姓名</button>
</template>
setup() {
  const state = reactive({
    name: '张三',
    age: 19
  })

  let { name } = state

  const clickHandle = () => {
    name = '李四'
  }

  return {
    name,
    clickHandle
  }
}

利用toRef进行处理

setup() {
  const state = reactive({
    name: '张三',
    age: 19
  })

  let name = toRef(state, 'name')

  const clickHandle = () => {
    // 需要通过value访问到具体数据
    name.value = '李四'
  }

  return {
    name,
    clickHandle
  }
}

toRefs

将响应对象内所有属性处理为响应式属性

<template>
  <h1>用户:{{ name }}</h1>
  <button @click="clickHandle">修改姓名</button>
</template>
setup() {
  const state = reactive({
    name: '张三',
    age: 19
  })

  const clickHandle = () => {
    console.log(123)
    state.name = '李四'
  }

  return {
    ...toRefs(state),
    clickHandle
  }
}

ref

常用语将普通数据处理为响应式数据

  1. 引入ref

    import { ref } from 'vue'
  2. 通过ref定义响应式数据

    setup() {
      // ref函数可传递数据的默认值
      const count = ref(10)
      return {
        count
      }
    }
  3. 使用数据

    <template>
      <h1>{{ count }}</h1>
      <button @click="clickHandle">修改count</button>
    </template>
  4. 修改数据(需要通过数据.value进行修改)

    setup() {
      const count = ref(10)
    
      const clickHandle = () => {
        count.value++
      }
    
      return {
        count,
        clickHandle
      }
    }

06-生命周期函数

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

import { onUpdated, onUnmounted } from 'vue'
export default {
  name: 'App',
  setup() {

    onBeforeMount(() => {
      console.log(document.querySelector('#custom-h1')) // null
    })

    onMounted(() => {
      console.log(document.querySelector('#custom-h1')) // <h1 id="custom-h1">hello</h1>
    })
  }
}

与 2.x 版本生命周期相对应的组合式 API

  • beforeCreate -> 使用 setup()
  • created -> 使用 setup()
  • beforeMount -> onBeforeMount
  • mounted -> onMounted
  • beforeUpdate -> onBeforeUpdate
  • updated -> onUpdated
  • beforeDestroy -> onBeforeUnmount
  • destroyed -> onUnmounted

07-案例

image-20210724221332768

案例分析:

  1. 定义数据并且渲染数据,定义的数据需要为响应式,因为鼠标移动数据会发生变化
  2. 鼠标移动修改数据,给文档绑定鼠标移动事件(注意时机,页面一进来并且能操作dom)
  3. 通过获取事件对象的信息获取到坐标并赋值给数据
  4. 组件销毁卸载事件

1. 定义数据并渲染

<template>
  <h1>鼠标的X:{{ x }}</h1>
  <h1>鼠标的Y:{{ y }}</h1>
</template>
<script>
import { reactive, toRefs } from 'vue'
export default {
 name: 'App',
  setup() {
    const mouse = reactive({
      x: 0,
      y: 0
    })
    return mouse
  }
}
</script>

2. 绑定鼠标移动事件

import { reactive, toRefs, onMounted } from 'vue'
export default {
 name: 'App',
  setup() {
    // 定义数据
    const mouse = reactive({
      x: 0,
      y: 0
    })

    // 鼠标移动修改数据
    const setDataOnMove = e => {
      console.log(e)
    } 

    // 绑定鼠标事件
    onMounted(() => {
      document.addEventListener('mousemove', setDataOnMove)
    })
    return mouse
  }
}

3. 获取数据并修改

// 鼠标移动修改数据
const setDataOnMove = e => {
  mouse.x = e.x
  mouse.y = e.y
}

4. 组件卸载销毁事件

// 1.4 组件消耗,删除事件
onUnmounted(()=>{
  document.removeEventListener('mousemove', move)
})

08-computed

引入computed

  1. 直接在setup内部定义

    <template>
      <h1>粉丝数量:{{ formatFans }}</h1>
    </template>
    <script>
    import { computed, ref } from 'vue'
    export default {
     name: 'App',
      setup() {
        const fans = ref(189987)
        // 利用计算属性进行计算
        const formatFans = computed(() => {
          return ((fans.value)/10000).toFixed(2) + '粉丝'
        })
        return {
          fans,
          formatFans
        }
      }
    }
    </script>
  2. 在reactive中定义

    <template>
      <h1>粉丝数量:{{ formatFans }}</h1>
    </template>
    <script>
    import { computed, reactive } from 'vue'
    export default {
     name: 'App',
      setup() {
        const state = reactive({
          fans: 189976,
          formatFans: computed(() => {
            return (state.fans / 10000).toFixed(2) + ''
          })
        })
        return state
      }
    }
    </script>

09-watch

数据监视

监视ref数据

<template>
  <h1>国籍: {{ nationality }}</h1>
  <button @click="setNationnality">修改国籍</button>
</template>
<script>
import { ref, watch } from 'vue'
export default {
 name: 'App',
  setup() {
    // 定义数据
    const nationality = ref('美国')
    // 修改数据
    const setNationnality = () => {
      nationality.value = '中国'
    }
    // 监视数据
    watch(nationality, (newVal, oldVal) => {
      console.log(newVal, oldVal)
    })
    return {
      nationality,
      setNationnality
    }
  }
}
</script>

监视reactive数据

<template>
  <h1>姓名: {{ person.name }}</h1>
  <h1>年龄 {{ person.age }}</h1>
  <button @click="setPersonName">修改姓名</button>
</template>
<script>
import { reactive, watch } from 'vue'
export default {
 name: 'App',
  setup() {
    // 定义数据
    const person = reactive({
      name: '张三',
      age: 19
    })
    // 修改数据
    const setPersonName = () => {
      person.name = '李四'
    }
    // 监视数据
    watch(person, (newVal, oldVal) => {
      console.log(newVal.name, oldVal.name) // 监视的是一个对象 newVal = oldVal
    })
    return {
      person,
      setPersonName
    }
  }
}
</script>

监视对象某个属性

// 监视某个属性
watch(() => person.name, (newVal, oldVal) => {
  // 监视的是一个对象 newVal = oldVal
  console.log('person的name那么变化了')
})

监视复杂数据类型中的某个复杂数据

<template>
  <h1>国籍: {{ nationality }}</h1>
  <button @click="setNationnality">修改国籍</button>

  <h1>姓名: {{ person.name }}</h1>
  <h1>年龄 {{ person.age }}</h1>
  <h1>儿子:{{ person.child[0] }}</h1>
  <h1>儿子:{{ person.child[1] }}</h1>
  <button @click="setPersonName">修改姓名</button>
  <button @click="setPersonAge">修改年龄</button>
  <button @click="setPersonChildName">修改小儿子的名字</button>
</template>
<script>
import { reactive, ref, watch } from 'vue'
export default {
 name: 'App',
  setup() {
    // 定义数据
    const person = reactive({
      name: '张三',
      age: 19,
      child: [
        '张小三',
        '张老三'
      ]
    })

    const setPersonChildName = () => {
      person.child[0] = '王小三'
    }
    // 监视复杂数据类型中的某个复杂数据类型
    watch(() => person.child, (newVal, oldVal) => {
      // 监视的是一个对象 newVal = oldVal
      console.log('person的儿子的name那么变化了')
    }, {
      deep: true, // 深度监听
      immediate: true // 默认触发一次
    })

    return {
      person,
      setPersonChildName
    }
  }
}
</script>

监视多个数据

// 监视多个数据
watch([person, nationality], (newVal, oldVal) => {
  console.log(newVal[1])
  console.log(oldVal[1])
  console.log('person或者nationnality变化了')
})

10-ref获取dom或组件

  • 定义一个空的响应式数据为了和某个dom或组件进行绑定

    import { onMounted, ref } from 'vue'
    export default {
     name: 'App',
      setup() {
        // 通过ref定义响应式数据
        const h1 = ref(null)
        // 在onMounted操作dom
        onMounted(() => {
          console.log(h1.value)
        })
        return {
          h1
        }
      }
    }
  • 将响应式数据在模版中将某个dom或组件通过ref进行绑定

    注意:不需要动态绑定

    <h1 ref="h1">hello</h1>

11-组件通讯

父传子props

  • 父组件通过属性绑定传动

    <template>
      <h1>父组件的钱: {{ money }}</h1>
      <child-a :money="money"></child-a>
    </template>
    <script>
    import { ref } from 'vue'
    import ChildA from './components/child-a.vue'
    export default {
     name: 'App',
      setup() {
        const money = ref(1000000)
        return {
          money
        }
      },
      components: {
        ChildA
      }
    }
    </script>
  • 子组件通过props接收

    <template>
      <h1>子组件的钱: {{ money }}</h1>
    </template>
    <script>
    export default {
      name: 'child-a',
      props: {
        money: {
          type: Number,
          required: true
        }
      },
      setup() {
        return {
        }
      }
    }
    </script>
  • 子组件也可以通过setup行参获取到

    setup(props) {
      console.log(props.money)
      return {
      }
    }

子向父传值

  • 子组件通过context.emit触发自定义事件

    <template>
      <h1>子组件的钱: {{ money }}</h1>
      <button @click="subMoney">花了50</button>
    </template>
    <script>
    export default {
      name: 'child-a',
      props: {
        money: {
          type: Number,
          required: true
        }
      },
      setup(props, context) {
        // context 是一个普通的 JavaScript 对象,它暴露组件的三个 property
        // Attribute (非响应式对象)
        console.log(context.attrs)
        // 插槽 (非响应式对象)
        console.log(context.slots)
        // 触发事件 (方法)
        console.log(context.emit)
        const subMoney = () => {
          context.emit('change-money', -50)
        }
        return {
          subMoney
        }
      }
    }
    </script>
  • 父组件通过定义自定义事件的函数接收数据,并处理业务逻辑

    <template>
      <h1>父组件的钱: {{ money }}</h1>
      <!-- 定义自定义事件 -->  
      <child-a :money="money" @change-money="changeMoney">
      </child-a>
    </template>
    <script>
    import { ref } from 'vue'
    import ChildA from './components/child-a.vue'
    export default {
      name: 'App',
      setup() {
        let money = ref(1000000)
        // 通过事件函数的行参获取到子组件传递过来的数据
        const changeMoney = newMoney => {
          console.log(newMoney)
          money.value += newMoney
        }
        return {
          money,
          changeMoney
        }
      },
      components: {
        ChildA
      }
    }
    </script>
  • 警告说明:我们需要告知组件哪些是自定义事件

    Extraneous non-emits event listeners (changeMoney) were passed to component but could not be automatically inherited because component renders fragment or text root nodes. If the listener is intended to be a component custom event listener only, declare it using the "emits"
  • 在子组件内部定义emits

    name: 'App',
    emits: ['change-money']

依赖注入

依赖注入并不是vue3.0新增的特性,2.0就开始支持,只不过使用方式有所区别

传数据

  1. 引入provide进行提供数据

    import { provide } from 'vue'
    setup () {
      const money = ref(10000)
      provide('money', money)
      return {
        money
      }
    }
  2. 子组建、孙子组件通过inject进行接收

    import { inject } from 'vue'
    export default {
      setup() {
        const money = inject('money')
        return {
          money
        }
      }
    }

父组件传方法供子组件调用修改数据

  1. 提供方法

    setup () {
      let money = ref(10000)
      // 修改money数据
      const setMoney = newMoney => {
        money.value = newMoney
      }
      provide('money', money)
      // 提供方法
      provide('setMoney', setMoney)
      return {
        money
      }
    }
  2. 子组件消费

    <template>
      <h1>孙子:{{ money }}</h1>
      <button @click="clickHandle">改money</button>
    </template>
    <script>
    import { inject } from 'vue'
    export default {
     name: 'App',
      setup() {
        const money = inject('money')
        // 获取provide提供的方法
        const setMoney = inject('setMoney')
    
        const clickHandle = () => {
          // 点击调用
          setMoney(10)
        }
        return {
          money,
          clickHandle
        }
      }
    }
    </script>

路由

vue3中的路由使用方式变成了导入方法,进行路由的使用

import { createRouter, createWebHistory } from 'vue-router'

const routes = [
]

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

export default router

createRouter: 用来创建路由对象

createWebHistory:用来使用history模式的路由

createWebHashHistory:用来使用hash模式的路由

useRouter

使用useRouter进行编程式导航

import { useRouter } from 'vue-router'
setup () {
  const router = useRouter()

  const goHome = () => {
    router.push('/home')
  }
}

useRoute

通过useRoute获取当前路由信息,path、query、params

import { useRoute } from 'vue-router'
setup () {
  const route = useRoute()
  console.log(route.path)
}
1
https://gitee.com/destiny001/vue3.0.git
git@gitee.com:destiny001/vue3.0.git
destiny001
vue3.0
vue3.0
master

搜索帮助