本项目综合运用了 Vue3.0
的新特性,适合新手学习😁javascript
Composition API
即 Function-based API
进行改造,配合 Vue Cli
,优先体验 Vue3
特性axios
库进行网络请求,weui
库实现 UI 界面# 安装依赖 npm install # 在浏览器打开localhost:8080查看页面,并实时热更新 npm run serve # 发布项目 npm run build
建议配合 Visual Studio Code 和 Vue 3 Snippets 代码插件食用Ψ( ̄∀ ̄)Ψ。html
如下是项目运用到的依赖,@vue/composition-api
配合 vue
模块让咱们 Vue2.0
版本能够抢先体验 Vue3.0
的新特性,axios
是辅助咱们发送网络请求获得数据的工具库,weui
是一套与微信原生视觉一致的基础样式库,方便咱们快速搭建项目页面。vue
"@vue/composition-api": "^0.3.4", "axios": "^0.19.0", "core-js": "^3.4.3", "vue": "^2.6.10", "weui": "^2.1.3"
├── src │ ├── App.vue # 组件入口 │ ├── assets # 资源目录 │ ├── stores/index.js # 状态管理 │ ├── components # 组件目录 │ │ ├── Header.vue # 头部组件 │ │ ├── Search.vue # 搜索框组件 │ │ ├── Panel.vue # 列表组件 │ ├── main.js # 项目入口 ├── public # 模板文件 ├── vue.config.js # 脚手架配置文件 ├── screenshot # 程序截图
npm install @vue/composition-api --save
使用 npm
命令下载了 @vue/composition-api
插件之后,引入该模块后,须要显式调用 Vue.use(VueCompositionApi)
,按照文档在 main.js
引用便开启了 Composition API
的能力。java
// main.js import Vue from 'vue' import App from './App.vue' // 1.引入Composition API模块 import VueCompositionApi from '@vue/composition-api' Vue.config.productionTip = false // 2.不要漏了显式调用 VueCompositionApi Vue.use(VueCompositionApi) new Vue({ render: h => h(App), }).$mount('#app')
npm install weui --save
咱们一样使用 npm
安装 weui
模块,而后在 main.js
中引入 weui
的基础样式库,方便咱们能够在全局使用微信基础样式构建项目页面。node
// main.js import Vue from 'vue' import App from './App.vue' // 全局引入 `weui` 的基础样式库 import 'weui' import VueCompositionApi from '@vue/composition-api' Vue.config.productionTip = false Vue.use(VueCompositionApi) new Vue({ render: h => h(App), }).$mount('#app')
回到 App.vue
,保留 components
属性值清空 <template>
模板的内容,删除 <style>
模板,等待从新引入新的组件。react
<template> <div id="app"> Hello World </div> </template> <script> export default { name: "app", components: {} }; </script>
在 src/components
目录下新建第一个组件,取名为 Header.vue
写入如下代码,点击查看源代码:ios
<template> <header :style="{ backgroundColor: color?color:defaultColor }">{{title}}</header> </template> <script> import { reactive } from "@vue/composition-api"; export default { // 父组件传递进来更改该头部组件的属性值 props: { // 标题 title: String, // 颜色 color: String }, setup() { const state = reactive({ defaultColor: "red" }); return { ...state }; } }; </script> <style scoped> header { height: 50px; width: 100%; line-height: 50px; text-align: center; color: white; } </style>
这里运用了一个全新的属性 setup
,这是一个组件的入口,让咱们能够运用 Vue3.0
暴露的新接口,它运行在组件被实例化时候,props
属性被定义以后,实际上等价于 Vue2.0
版本的 beforeCreate
和 Created
这两个生命周期,setup
返回的是一个对象,里面的全部被返回的属性值,都会被合并到 Vue2.0
的 render
渲染函数里面,在单文件组件中,它将配合 <template>
模板的内容,完成 Model
到 View
之间的绑定,在将来版本中应该还会支持返回 JSX
代码片断。git
<template> <!-- View --> <div>{{name}}</div> </template> <script> import { reactive } from '@vue/composition-api' export default { setup() { const state = reactive({ name: 'Eno Yao' }); // return 暴露到 template 中 return { // Model ...state } } } </script>
在 setup
函数里面, 咱们适应了 Vue3.0 的第一个新接口 reactive
它主要是处理你的对象让它通过 Proxy
的加工变为一个响应式的对象,相似于 Vue2.0
版本的 data
属性,须要注意的是加工后的对象跟原对象是不相等的,而且加工后的对象属于深度克隆的对象。github
const state = reactive({ name: 'Eno Yao' })
在 Vue2.0
中咱们可使用 props
属性值完成父子通讯,在这里咱们须要定义 props
属性去定义接受值的类型,而后咱们能够利用 setup
的第一个参数获取 props
使用。ajax
export default { props: { // 标题 title: String, // 颜色 color: String }, setup(props) { // 这里可使用父组件传过来的 props 属性值 } };
咱们在 App.vue
里面就可使用该头部组件,有了上面的 props
咱们能够根据传进来的值,让这个头部组件呈现不一样的状态。
<template> <div id="app"> <!-- 复用组件,并传入 props 值,让组件呈现对应的状态 --> <Header title="Eno" color="red" /> <Header title="Yao" color="blue" /> <Header title="Wscats" color="yellow" /> </div> </template> <script> import Header from "./components/Header.vue"; export default { name: "app", components: { Header, } }; </script>
setup
函数的第二个参数是一个上下文对象,这个上下文对象中包含了一些有用的属性,这些属性在 Vue2.0
中须要经过 this
才能访问到,在 vue3.0
中,访问他们变成如下形式:
setup(props, ctx) { console.log(ctx) // 在 setup() 函数中没法访问到 this console.log(this) // undefined }
具体能访问到如下有用的属性:
完成上面的 Header.vue
咱们就编写 Search.vue
搜索框组件,继续再 src/components
文件夹下面新建 Search.vue
文件,点击查看源代码。
<template> <div :class="['weui-search-bar', {'weui-search-bar_focusing' : isFocus}]" id="searchBar"> <form class="weui-search-bar__form"> <div class="weui-search-bar__box"> <i class="weui-icon-search"></i> <input v-model="searchValue" ref="inputElement" type="search" class="weui-search-bar__input" id="searchInput" placeholder="搜索" required /> <a href="javascript:" class="weui-icon-clear" id="searchClear"></a> </div> <label @click="toggle" class="weui-search-bar__label" id="searchText"> <i class="weui-icon-search"></i> <span>搜索</span> </label> </form> <a @click="toggle" href="javascript:" class="weui-search-bar__cancel-btn" id="searchCancel">取消</a> </div> </template> <script> import { reactive, toRefs, watch } from "@vue/composition-api"; import store from "../stores"; export default { // setup至关于2.x版本的beforeCreate生命周期 setup() { // reactive() 函数接收一个普通对象,返回一个响应式的数据对象 const state = reactive({ searchValue: "", // 搜索框两个状态,聚焦和非聚焦 isFocus: false, inputElement: null }); // 切换搜索框状态的方法 const toggle = () => { // 让点击搜索后出现的输入框自动聚焦 state.inputElement.focus(); state.isFocus = !state.isFocus; }; // 监听搜索框的值 watch( () => { return state.searchValue; }, () => { // 存储输入框到状态 store 中心,用于组件通讯 store.setSearchValue(state.searchValue); // window.console.log(state.searchValue); } ); return { // 将 state 上的每一个属性,都转化为 ref 形式的响应式数据 ...toRefs(state), toggle }; } }; </script>
能够看到咱们上面用了不少的新属性,咱们先介绍 toRefs
,函数能够将 reactive()
建立出来的响应式对象,转换为普通的对象,只不过,这个对象上的每一个属性节点,都是 ref()
类型的响应式数据,配合 v-model
指令能完成数据的双向绑定,在开发中很是高效。
import { reactive, toRefs } from "@vue/composition-api"; export default { setup() { const state = reactive({ name: 'Eno Yao' }) } return { // 直接返回 state 那么数据会是非响应式的, MV 单向绑定 // ...state, // toRefs 包装后返回 state 那么数据会是响应式的, MVVM 双向绑定 ...toRefs(state), }; }
这里的输入框拥有两个状态,一个是有输入框的状态和无输入框的状态,因此咱们须要一个布尔值 isFocus
来控制状态,封装了一个 toggle
方法,让 isFocus
值切换真和假两个状态。
const toggle = () => { // isFocus 值取反 state.isFocus = !state.isFocus; };
而后配合 v-bind:class
指令,让 weui-search-bar_focusing
类名根据 isFocus
值决定是否出现,从而更改搜索框的状态。
<div :class="['weui-search-bar', {'weui-search-bar_focusing' : isFocus}]" id="searchBar">
这里的搜索输入框放入了 v-model
指令,用于接收用户的输入信息,方便后面配合列表组件执行检索逻辑,还放入了 ref
属性,用于获取该 <input/>
标签的元素节点,配合state.inputElement.focus()
原生方法,在切换搜索框状态的时候光标自动聚焦到输入框,加强用户体验。
<input v-model="searchValue" ref="inputElement" />
watch()
函数用来监视某些数据项的变化,从而触发某些特定的操做,使用以前仍是须要按需导入,监听 searchValue
的变化,而后触发回调函数里面的逻辑,也就是监听用户输入的检索值,而后触发回调函数的逻辑把 searchValue
值存进咱们建立 store
对象里面,方面后面和 Panel.vue
列表组件进行数据通讯:
import { reactive, watch } from "@vue/composition-api"; import store from "../stores"; export default { setup() { const state = reactive({ searchValue: "", }); // 监听搜索框的值 watch( () => { return state.searchValue; }, () => { // 存储输入框到状态 store 中心,用于组件通讯 store.setSearchValue(state.searchValue); } ); return { ...toRefs(state) }; } };
在这里咱们维护一份数据来实现共享状态管理,也就是说咱们新建一个 store.js
暴露出一个 store
对象共享 Panel
和 Search
组件的 searchValue
值,当 Search.vue
组件从输入框接受到 searchValue
检索值,就放到 store.js
的 store
对象中,而后把该对象注入到 Search
组件中,那么两个组件均可以共享 store
对象中的值,为了方便调试咱们还分别封装了 setSearchValue
和 getSearchValue
来去操做该 store
对象,这样咱们就能够跟踪状态的改变。
// store.js export default { state: { searchValue: "" }, // 设置搜索框的值 setSearchValue(value) { this.state.searchValue = value }, // 获取搜索框的值 getSearchValue() { return this.state.searchValue } }
完成上面的 Search.vue
咱们紧接着编写 Panel.vue
搜索框组件,继续再 src/components
文件夹下面新建 Panel.vue
文件,点击查看源代码。
<template> <div class="weui-panel weui-panel_access"> <div v-for="(n,index) in newComputed" :key="index" class="weui-panel__bd"> <a href="javascript:void(0);" class="weui-media-box weui-media-box_appmsg"> <div class="weui-media-box__hd"> <img class="weui-media-box__thumb" :src="n.author.avatar_url" alt /> </div> <div class="weui-media-box__bd"> <h4 class="weui-media-box__title" v-text="n.title"></h4> <p class="weui-media-box__desc" v-text="n.author.loginname"></p> </div> </a> </div> <div @click="loadMore" class="weui-panel__ft"> <a href="javascript:void(0);" class="weui-cell weui-cell_access weui-cell_link"> <div class="weui-cell__bd">查看更多</div> <span class="weui-cell__ft"></span> </a> </div> </div> </template> <script> import { reactive, toRefs, onMounted, computed } from "@vue/composition-api"; import axios from "axios"; import store from "../stores"; export default { setup() { const state = reactive({ // 页数 page: 1, // 列表数据 news: [], // 经过搜索框的值去筛选劣列表数据 newComputed: computed(() => { // 判断是否输入框是否输入了筛选条件,若是没有返回原始的 news 数组 if (store.state.searchValue) { return state.news.filter(item => { if (item.title.indexOf(store.state.searchValue) >= 0) { return item; } }); } else { return state.news; } }), searchValue: store.state }); // 发送 ajax 请求获取列表数据 const loadMore = async () => { // 获取列表数据 let data = await axios.get("https://cnodejs.org/api/v1/topics", { params: { // 每一页的主题数量 limit: 10, // 页数 page: state.page } }); // 叠加页数 state.page += 1; state.news = [...state.news, ...data.data.data]; }; onMounted(() => { // 首屏加载的时候触发请求 loadMore(); }); return { // 让数据保持响应式 ...toRefs(state), // 查看更多事件 loadMore }; } }; </script>
Vue3.0
的生命周期钩子和以前不同,新版本都是以 onXxx()
函数注册使用,一样须要局部引入生命周期的对应模块:
import { onMounted, onUpdated, onUnmounted } from "@vue/composition-api"; export default { setup() { const loadMore = () => {}; onMounted(() => { loadMore(); }); onUpdated(() => { console.log('updated!') }) onUnmounted(() => { console.log('unmounted!') }) return { loadMore }; } };
如下是新旧版本生命周期的对比:
beforeCreate
</s> -> use setup()
created
</s> -> use setup()
beforeMount
-> onBeforeMount
mounted
-> onMounted
beforeUpdate
-> onBeforeUpdate
updated
-> onUpdated
beforeDestroy
-> onBeforeUnmount
destroyed
-> onUnmounted
errorCaptured
-> onErrorCaptured
同时新版本还提供了两个全新的生命周期帮助咱们去调试代码:
在 Panel
列表组件中,咱们注册 onMounted
生命周期,并在里面触发请求方法 loadMore
以便从后端获取数据到数据层,这里咱们使用的是 axios
网络请求库,因此咱们须要安装该模块:
npm install axios --save
封装了一个请求列表数据方法,接口指向的是 Cnode
官网提供的 API
,因为 axios
返回的是 Promise
,因此配合 async
和 await
能够完美的编写异步逻辑,而后结合onMounted
生命周期触发,并将方法绑定到视图层的查看更多按钮上,就能够完成列表首次的加载和点击查看更多的懒加载功能。
// 发送 ajax 请求获取列表数据 const loadMore = async () => { // 获取列表数据 let data = await axios.get("https://cnodejs.org/api/v1/topics", { params: { // 每一页的主题数量 limit: 10, // 页数 page: state.page } }); // 叠加页数 state.page += 1; // 合并列表数据 state.news = [...state.news, ...data.data.data]; }; onMounted(() => { // 首屏加载的时候触发请求 loadMore(); });
接下来咱们就使用另一个属性 computed
计算属性,跟 Vue2.0
的使用方式很相近,一样须要按需导入该模块:
import { computed } from '@vue/composition-api';
计算属性分两种,只读计算属性和可读可写计算属性:
// 只读计算属性 let newsComputed = computed(() => news.value + 1) // 可读可写 let newsComputed = computed({ // 取值函数 get: () => news.value + 2, // 赋值函数 set: val => { news.value = news.value - 3 } })
这里咱们使用可读可写计算属性去处理列表数据,还记得咱们上一个组件 Search.vue
吗,咱们能够结合用户在搜索框输入的检索值,配合 computed
计算属性来筛选对咱们用户有用列表数据,因此咱们首先从 store
的共享实例里面拿到 Search.vue
搜索框共享的 searchValue
,而后利用原生字符串方法 indexOf
和 数组方法 filter
来过滤列表的数据,而后从新返回新的列表数据 newsComputed
,并在视图层上配合 v-for
指令去渲染新的列表数据,这样作既能够在没搜索框检索值的时候返回原列表数据 news
,而在有搜索框检索值的时候返回新列表数据 newsComputed
。
import store from "../stores"; export default { setup() { const state = reactive({ // 原列表数据 news: [], // 经过搜索框的值去筛选后的新列表数据 newsComputed: computed(() => { // 判断是否输入框是否输入了筛选条件,若是没有返回原始的 news 数组 if (store.state.searchValue) { return state.news.filter(item => { if (item.title.indexOf(store.state.searchValue) >= 0) { return item; } }); } else { return state.news; } }), searchValue: store.state }); } }
若是文章和笔记能带您一丝帮助或者启发,请不要吝啬你的赞和 Star,你的确定是我前进的最大动力😁