Skip to content
master
Switch branches/tags
Code

Files

Permalink
Failed to load latest commit information.
Type
Name
Latest commit message
Commit time
 
 
 
 
 
 
src
 
 
 
 
 
 
 
 

Quick Start

本项目综�?��?用了 Vue3.0 的新特性,适�?�新手学习�?

  • 基于 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 代�?�?�件食用Ψ( ̄∀ ̄)Ψ。

Dependencies

以下是项目�?用到的�?赖,@vue/composition-api �?�?� vue 模�?�让我们 Vue2.0 版本�?�以抢先体验 Vue3.0 的新特性,axios 是辅助我们�?��?网络请求得到数�?�的工具库,weui是一套与微信原生视觉一致的基础样�?库,方便我们快速�?�建项目页�?�。

"@vue/composition-api": "^0.3.4",
"axios": "^0.19.0",
"core-js": "^3.4.3",
"vue": "^2.6.10",
"weui": "^2.1.3"

Directory Structure

├── src
│   ├── App.vue                          # 组件入�?�
│   ├── assets                           # 资�?目录
│   ├── stores/index.js                  # 状�?管�?�
│   ├── components                       # 组件目录
│   │   ├── Header.vue                   # 头部组件
│   │   ├── Search.vue                   # �?�索框组件
│   │   ├── Panel.vue                    # 列表组件
│   ├── main.js                          # 项目入�?�
├── public                               # 模�?�文件
├── vue.config.js                        # 脚手架�?置文件
├── screenshot                           # 程�?截图

Composition API

npm install @vue/composition-api --save

使用 npm 命令下载了 @vue/composition-api �?�件以�?�,引入该模�?��?�,需�?显�?调用 Vue.use(VueCompositionApi) ,按照文档在 main.js 引用便开�?�了 Composition API 的能力。

// 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的基础样�?库,方便我们�?�以在全局使用微信基础样�?构建项目页�?�。

// 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> 模�?�,等待�?新引入新的组件。

<template>
  <div id="app">
    Hello World
  </div>
</template>
<script>
export default {
  name: "app",
  components: {}
};
</script>

在 src/components 目录下新建第一个组件,�?��??为 Header.vue 写入以下代�?,点击查看�?代�?:

<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

这里�?用了一个全新的属性 setup ,这是一个组件的入�?�,让我们�?�以�?用 Vue3.0 暴露的新接�?�,它�?行在组件被实例化时候,props 属性被定义之�?�,实际上等价于 Vue2.0 版本的 beforeCreate 和 Created 这两个生命周期,setup 返回的是一个对象,里�?�的所有被返回的属性值,都会被�?�并到 Vue2.0 的 render 渲染函数里�?�,在�?�文件组件中,它将�?�?� <template> 模�?�的内容,完�? Model 到 View 之间的绑定,在未�?�版本中应该还会支�?返回 JSX 代�?片段。

<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>

reactive

在 setup 函数里�?�, 我们适应了 Vue3.0 的第一个新接�?� reactive 它主�?是处�?�你的对象让它�?过 Proxy 的加工�?�为一个�?应�?的对象,类似于 Vue2.0 版本的 data 属性,需�?注�?的是加工�?�的对象跟原对象是�?相等的,并且加工�?�的对象属于深度克隆的对象。

const state = reactive({ name: 'Eno Yao' })

props

在 Vue2.0 中我们�?�以使用 props 属性值完�?父�?通信,在这里我们需�?定义 props 属性去定义接�?�值的类型,然�?�我们�?�以利用 setup 的第一个�?�数获�?� props 使用。

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>

context

setup 函数的第二个�?�数是一个上下文对象,这个上下文对象中包�?�了一些有用的属性,这些属性在 Vue2.0 中需�?通过 this �?能访问到,在 vue3.0 中,访问他们�?��?以下形�?:

setup(props, ctx) {
  console.log(ctx) // 在 setup() 函数中无法访问到 this
  console.log(this) // undefined
}

具体能访问到以下有用的属性:

  • root
  • parent
  • refs
  • attrs
  • listeners
  • isServer
  • ssrContext
  • emit

完�?上�?�的 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

�?�以看到我们上�?�用了很多的新属性,我们先介�? 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),
  };
}

template refs

这里的输入框拥有两个状�?,一个是有输入框的状�?和无输入框的状�?,所以我们需�?一个布尔值 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

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)
    };
  }
};

state management

在这里我们维护一份数�?��?�实现共享状�?管�?�,也就是说我们新建一个 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>

lifecycle hooks

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 -> use setup()
  • created -> use setup()
  • beforeMount -> onBeforeMount
  • mounted -> onMounted
  • beforeUpdate -> onBeforeUpdate
  • updated -> onUpdated
  • beforeDestroy -> onBeforeUnmount
  • destroyed -> onUnmounted
  • errorCaptured -> onErrorCaptured

�?�时新版本还�??供了两个全新的生命周期帮助我们去调试代�?:

  • onRenderTracked
  • onRenderTriggered

在 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

接下�?�我们就使用�?�外一个属性 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
    });
  }
}

License

Copyright(C) 2019, Vue Cli is released under the MIT.

About

📃基于 Vue3.0 Composition Api 快速构建实战项目

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published