實戰Vue簡易項目(4)定義視圖

視圖

包含內容#NavigationBar#TabBar#MainContextcss

爲何#NavigationBar#TabBar分在Layout中,而不是components中?vue

代碼上其實是沒有差異的,只是認爲#NavigationBar#TabBar是加載一次的,而非複用,且屬於頁面佈局內容。webpack

App.vue

Vue實例化的根組件,咱們在這裏進行佈局:web

src/App.vue文件:json

<template>
  <div id="app">
    <navigation-bar></navigation-bar>
    <router-view></router-view>
    <tab-bar></tab-bar>
  </div>
</template>
<script>
import TabBar from "@/views/layout/TabBar";
import NavigationBar from "@/views/layout/NavigationBar";
export default {
  name: 'App',
  components: {
    'navigation-bar': NavigationBar,
    'tab-bar':TabBar,
  }
}
</script>
<style>
 ...//未動原有樣式;
</style>
  • 在這裏,咱們使用<template />標識 其內部的HTML爲Vue Template。
  • <template />內部必有一個且惟一的節點(這裏是div#app)包裹內容(即便只是一串字符)-->若存在同級節點,則會報錯(這是由於VNode會經過createElement('div')來建立真實節點,只能是單個元素);
  • 經過components屬性以鍵值對的形式引入組件,模板(HTML)中使用的標籤名鍵名(自定義元素VNode),值爲導入的組件模塊;
  • 經過components定義組件使用的方式,限制了組件應用的範圍。即:若是你在其它文件直接使用<navigation-bar></navigation-bar>,控制檯會報錯:組件未註冊-->這就是組件的局部註冊
  • 局部註冊的組件要求:若是在某一文件中應用該組件,必需要使用components註冊一次。
  • 導入組件import TabBar from "@/views/layout/TabBar";路徑以@起,這是由於build/webpack.base.conf.js中配置的路徑別名'@' === "resolve('src')"
resolve: {
    extensions: ['.js', '.vue', '.json'],
    alias: {
      'vue$': 'vue/dist/vue.esm.js',
      '@': resolve('src'), //能夠追加當前項目下,想快捷訪問的文件目錄
    }
  },

臨時定義的組件文件:segmentfault

src/views/layout/NavigationBar.vue文件:數組

<template>
  <header>NavigationBar</header>
</template>
<style scoped>
  header{
    border-bottom: 4px solid #821910;
  }
</style>
  • 在這裏,咱們經過style[scoped]定義一份樣式,其做用範圍僅限於當前文件(又可稱模塊)模板中的元素。
  • 像下邊,在TabBar.vue中的header元素就沒有使用到該文件中的對應樣式,這就是局部做用域的樣式。
  • 局部做用域的樣式只對當前文件<template/>中的元素起做用,想改變body的樣式,很差意思,請全局導入或不使用局部做用域。

src/views/layout/TabBar.vue文件:app

<template>
  <div>
    <header>測試是否和NavigationBar同樣的效果</header>
    <footer>TabBar</footer>
  </div>
</template>
<style scoped>
  footer{
    border-bottom: 4px solid #07776e;
  }
</style>

顯示效果:

App

NavigationBar

#NavigationBar中分左右結構,左邊按鈕後退,右邊按鈕更新頁面。ide

更新頁面只是更新數據,而不是整個頁面的刷新,每一個頁面更新數據的接口不一樣,因此,要做爲組件屬性傳入。函數

src/views/layout/navigationBar.vue中:

<template>
  <header class="navigation_bar">
    <button @click="goBack" class="navigation_back">
      <i class="arrow"></i>
      <span class="back_tip">關閉</span>
    </button>
    <h2 v-if="hasTitle" class="navigation_title">{{title}}</h2>
    <button class="refresh" @click="onRefresh">刷新</button>
  </header>
</template>
  • 該部分爲單文件組件#NavigationBar的Template部分。
  • @clickv-on:click的簡寫,用於綁定點擊事件。
  • v-ifVue中的條件指令,根據返回的布爾值動態添加或移除DOM元素。
<script>
  /**
   * @title:頭部標題;
   * @refresh:刷新處理函數;
   */
  export default {
    props: ['title', 'refresh'],
    computed: {
      hasTitle() {
        return this.title && this.title.trim();
      },
    },
    methods: {
      goBack() {
        this.$router.back()
      },
      onRefresh() {
        this.refresh();
      },
    },
  }
</script>
  • 該部分爲單文件組件#NavigationBar的組件配置對象。
  • props爲父級(調用該組件的組件)傳過來的屬性。

    • 傳值方式<navigation-bar title="我是標題" :refresh="refresh"></navigation-bar>(須要在src/App.vue中定義refresh函數)

      • title傳的值爲字符串,不須要:前綴;
      • :refresh傳的值爲非字符串(數字、布爾值、函數、數組、對象...),:爲前綴,值爲Javascript表達式計算結果;
    • 在程序中,如this.title引用props的值。
    • 在模板中,做元素的innerHTML內容時,如{{title}}引用。
  • methods爲該組件內,元素綁定的事件處理函數。

    • 在程序中,如this.refresh()引用。
    • 在模板中,如@click="onRefresh"調用,傳入的是函數應用;若傳參,如@click="onRefresh(param)"調用。
  • computed自己寫法和函數定義一致,然而,其自己是一個data(數據源),字段名爲函數名,值爲函數的返回值。

    • 使用方式與props一致。
區別 method computed
類型 函數 數據變量
參數 能夠帶參 不帶參(非函)
觸發 交互時觸發 聲明內部的this屬性的值變化時執行

顯示效果

這裏樣式請你們隨意設定,我使用的是flexBox佈局。

點擊刷新,我定義了console.log('refresh success')

Navigation

TabBar

#TabBar分如下狀況:

  • 一個按鈕
  • 兩個按鈕

每一個視圖中#TabBar按鈕是不一樣的,因此,按鈕的配置要看成組件屬性傳入(控制變化的量)。

測試數據源

const tabBars = [
  {
    label: '提交',
    eventType: 'click',
    disabled: false,
    callBack(vm) {
      console.log('單擊,提交');
    }
  },
  {
    label: '取消',
    eventType: 'dblclick', //該事件在手機模式下沒法響應呢,只能在PC模式下調試
    disabled: false,
    callBack(vm) {
      console.log('雙擊,取消');
    }
  }
]

src/views/layout/TabBar.vue的模板:

<template>
  <footer class="tab-bar" v-if="isRender">
    <div class="tab-button" v-for="tab in tabBars" :key="tab.label">
      <template>
        <tab-button :el="$parent" :disabled="tab.disabled" :event="tab.eventType" :callBack="tab.callBack">
          <span>{{tab.label}}</span>
        </tab-button>
      </template>
    </div>
  </footer>
</template>
  • v-for="tab in tabBars"Vue中的循環結構,搭配:key使用,優化Vue的渲染機制;

    • tabBars進行遍歷,tab爲數組中的元素。
    • 一樣key值,在更新時,會複用組件,而不是銷燬後,再建立一個新的組件。
  • <tab-button :el="$parent" :disabled="tab.disabled" :event="tab.eventType" :callBack="tab.callBack">這是是一個新組件的引用。

    • $parent是組件實例#TabBar的父實例(#App)。

src/views/layout/TabBar.vue組件配置對象:

<script>
  const tabButton = {
    render(createElement) {
      return createElement(
        'button',
        {
          "class": this.className,
          on: {
            [this.event]: this.tabClick,
          },
        },
        this.$slots.default, //指代<span>{{tab.label}}</span>
      )
    },
    props: ['event', 'callBack', 'disabled','el'],
    computed: {
      className() {
        return this.disabled ? 'tab-label disabled' : 'tab-label';
      }
    },
    methods: {
      tabClick() {
        if (this.disabled) return;
        this.callBack && this.callBack(this.el)
      }
    }
  }
  export default {
    components: {
      'tab-button': tabButton
    },
    props: ['tabBars'],
    computed: {
      isRender() {
        const isRender = _.isArray(this.tabBars) && this.tabBars.length !== 0;
        return isRender;
      },
    },
  }
</script>
  • 這裏使用了另外一種方式定義組件tabButton,其與 單文件組件 的區別僅僅在於使用render方法定義模板

    • 優點:定義出來的組件更具備靈活性,在這裏on屬性能夠動態綁定事件類型

      • 注意:這裏的事件類型[this.event]是做爲參數傳進來的呢!
    • 組件本質上只是一個JavaScript對象(虛擬DOM),該對象按Vue規定的成員屬性構建,區別只在於Template的寫做模式。
  • 這裏應用了Slot,指代該組件嵌套的子節點。
  • 這裏使用了underscore.js_.isArray),須要在build/webpack.base.conf.js中配置:
const webpack = require('webpack');
...
module.exports = {
  ...
  module:{
    ...
  },
  plugins:[
    new webpack.ProvidePlugin({
      _: 'underscore',
    }),
  ],
  ...

而後,underscore在全局可用。

由於這裏的配置對devprod環境是一致的,因此,直接在build/webpack.base.conf.js中配置了。

顯示效果

tabBars

總體Layout佈局

最終,咱們要作一個頂天立地的內滾動結構(使用flexBox佈局便可):

內滾動結構

src/App.vue樣式中:

<style lang="scss" scoped>
@import "@/styles/mixins.scss";

#app {
  @include flex($direction: column);
}

.main-context {
  flex-grow: 1;
  overflow: hidden;
  overflow-y: auto;
}
</style>

其中src/styles/mixins.scss

@mixin flex($direction:row, $alignItems: stretch, $justifyContent: space-between, $basis: auto,$wrap:nowrap) {
  display: flex;
  flex-direction: $direction;
  align-items: $alignItems;
  justify-content: $justifyContent;
  flex-basis: $basis;
  flex-wrap: $wrap;
}

章節回顧

  • 我這裏面省略了將寫好的#NavigationBar#TabBar替換原臨時搭建的對應組件,我相信你能處理好,對吧?!
  • #App小節中,是怎樣註冊局部組件的,若是想要在項目全部模板中能夠直接使用標籤名來應用組件,該怎麼處理呢?
  • #App小節中,如何定義局部樣式的,若是想讓app.vue中header的樣式全局可用,該怎麼處理呢?
  • 父組件如何傳值給子組件,若想傳佈爾值,該如何操做?
  • render函數如何渲染組件模板,使用該方法如何定義組件?
  • slot用於什麼狀況下呢,有什麼好處?

思考

  • 接下來要實現列表了呢,怎麼作列表數據呢?

番外

相關文章
相關標籤/搜索