天天看點

Vuejs學習v1.0.3,持續更新中。。。

文章目錄

      • 原創内容,轉載請注明出處^_^
      • 基礎知識
        • 為什麼要學習Vuejs
        • 前後端分離暨Vuejs的優勢
          • 變革
          • 開發
          • 運維
          • 對比
        • 什麼是MVVM?MVVM是Model-View-ViewModel的縮寫
        • 檔案
      • 配置
        • 啟動配置
      • 元件
        • 概念
          • package.json.lock
          • vue-cli是vue.js的腳手架,用于自動生成vue.js+webpack的項目模闆
          • export default
          • $refs
        • 定義元件
        • 注冊元件
        • 生命周期
          • computed
        • 文法
          • Object.assign
      • 工具
        • 同步
          • Promise.all
          • Promise.race
        • 建構類
        • VueX
          • mutations
        • rules
        • Element-ui
          • 穿梭框踩坑
      • 踩坑
        • 錯誤記錄
          • 簡單
            • 代碼不起效
            • Error in created hook: "TypeError: _admin.menuTree.then is not a function"
            • 請求頭類型不對
            • 'vue-cli-service' 不是内部或外部指令,也不是可運作的程式
          • npm相關
          • Webpack相關
      • 簡易功能開發
        • 取消表單驗證提示
        • ajax
        • 登入
          • 邏輯
          • 源碼解析
            • 背景API切換回Mock需改動
        • Token - 令牌
        • 全局變量
        • 引入模闆
          • 彈窗
        • 編輯器
          • Json編輯器
        • 布局
          • 九宮格|十六宮格
      • 相容
        • 解決IE相容問題
      • 特殊代碼
        • cookie
版本号 作者 qq 備注
v1.0.2 飛豺 8416837 Vue 2.6.10

原創内容,轉載請注明出處_

基礎知識

為什麼要學習Vuejs

  • 網友:"到今天這個時代有些人學完了js、html5/dom/bom直接跳過jQ去學vue我覺得完全沒問題,是以不懂jQ的人會vue當然可以,本來前端的基礎就是js而不是jQ。而且vue本身走的就是子產品化的開發方式,就是讓項目更好維護,更好更新,在這些方面絕對比jQ更優秀。jQuery之是以被替代,一是開發方式保守、老舊、效率低,二是因為html5出了很多新的api,完全可以代替jQ,就連bootstrap重構都已經申明将剔除jQ。退一萬步說,前端最基礎的還是js,隻要你js技術過關,不管是學jQ還是vue都會很快。"

  • vue.js的作者尤雨溪是中國人,在知乎上有帳号,且非常活躍。

    尤雨溪畢業于上海複旦附中,在美國完成大學學業,大學畢業于Colgate University,後在Parsons設計學院獲得Design & Technology藝術碩士學位,現就職于紐約Google Creative Lab。

    2016年9月3日,南京JSConf,尤雨溪宣布加盟阿裡巴巴Weex團隊,尤雨溪稱他将以技術顧問的身份加入Weex 團隊來做 Vue 和 Weex 的 JavaScript runtime 整合,目标是Vue跨三端。

前後端分離暨Vuejs的優勢

變革
  • 解耦,視圖-網關-服務各司其職。自由組合搭配。
  • 後端無狀态,不存儲使用者資訊,不維護session減輕負擔。安全提升;
  • 單頁模式,簡化握手,響應更快;
  • 靜态頁面部署到nginx,響應更快,且更新時無感覺,不需重新開機;
  • Reactive程式設計。響應式,資料改變時,頁面實時響應。
  • 高效,将某些運算遷移到前端,類似邊緣計算-分攤中心壓力;
  • 簡化代碼,後端API機械式自動生成,前端調用API即可;
  • 技術健全,如路由、存儲、生命周期管控、監聽、計算、調試、測試架構,規範成熟;
  • 漸進式,自然融入到已經上線的項目,持續增加需求,擴充性強;
  • 資料渲染高效;
  • 優秀案例;
  • 技術革新、推廣、傳承;
開發
  • 上手較快,因為基礎還是JS;
  • 熱部署,代碼更新後即時生效而無需建構。傳統開發亦可以通過一定配置進行熱部署,但有時會失效。
  • 靈活與API|MQ互動;
  • 相容技術棧,如WebSocket、MQ等;
  • 豐富的元件庫,提升效率;
  • 調試、測試架構;
  • 脫離後端,暫無API時,可使用mock.js架構模拟;
  • 文法糖有助于實作複雜功能;
  • 專用IDE,如webstorm;
  • 文檔詳盡易懂,社群活躍壯大中;
  • 開發體驗:規範、生命周期控制,資料綁定,熱部署不需複雜設定,JS與DOM解耦,不用直接操作DOM,代碼複用,MVVM結構,面向對象-後端思維;
運維
  • 獨立部署,減輕後端壓力,釋出以及服務崩潰互不影響;
  • 松散耦合,定位BUG快捷;
  • 代碼易讀,易維護,新增需求無壓力,如多用戶端需求、前後端分離需求;
  • 建構靜态檔案,防止開發技術外洩;
對比
  • Thymeleaf、JSP與DOM耦合,前者遵循xml規範,模闆隻解決了渲染,沒解決麻煩的DOM問題;

什麼是MVVM?MVVM是Model-View-ViewModel的縮寫

要編寫可維護的前端代碼絕非易事。我們已經用MVC模式通過koa實作了後端資料、模闆頁面和控制器的分離,但是,對于前端來說,還不夠。

這裡有童鞋會問,不是講Node後端開發嗎?怎麼又回到前端開發了?

對于一個全棧開發工程師來說,懂前端才會開發出更好的後端程式(不懂前端的後端工程師會設計出非常難用的API),懂後端才會開發出更好的前端程式。程式設計的基本思想在前後端都是通用的,兩者并無本質的差別。這和“不想當廚子的裁縫不是好司機”是一個道理。

改變JavaScript對象的狀态,會導緻DOM結構作出對應的變化!這讓我們的關注點從如何操作DOM變成了如何更新JavaScript對象的狀态,而

操作JavaScript對象比DOM簡單

多了!

這就是MVVM的設計思想:

關注Model的變化,讓MVVM架構去自動更新DOM的狀态,進而把開發者從操作DOM的繁瑣步驟中解脫出來

! ——廖雪峰

ps:jQuery MVVM架構如JSViews

檔案

  • Vue中index.html、main.js、App.vue、index.js之前的關系以及加載過程

    App.vue中的router-view

<template>
  <div id="app">
    <p>就是一張da圖檔</p>
    [外鍊圖檔轉存失敗(img-dlDOl0R2-1562114250786)(https://mp.csdn.net/mdeditor/assets/logo.png)]
    <!--PS: router-view渲染路由資訊于此-->
    <router-view/>
  </div>
</template>
           

router/index.js定義了簡單的路由資訊

  • main.js

    入口,加載元件到index.html

配置

啟動配置

package.json檔案配置:

  • 啟動指令
"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "nodemon -e js,graphql -x node -r dotenv/config ./src/index.js",
    "debug": "nodemon -e js,graphql -x node --inspect -r dotenv/config ./src/index.js",
    "lint": "eslint --ext .js src"
  },
           

比如,執行

yarn start

則走

start

腳本

package-lock.json作用

元件

概念

package.json.lock

舉個例子:

“dependencies”: {

“@types/node”: “^8.0.33”,

},

這裡面的 向上标号^是定義了向後(新)相容依賴,指如果 types/node的版本是超過8.0.33,并在大版本号(8)上相同,就允許下載下傳最新版本的 types/node庫包,例如實際上可能運作npm install時候下載下傳的具體版本是8.0.35.

vue-cli是vue.js的腳手架,用于自動生成vue.js+webpack的項目模闆
  • 元件類似自定義元素.Web元件規範
  • 在一個大型應用中,有必要将整個應用程式劃分為元件,以使開發更易管理。假想例子,以便展示元件的結構↓
<div id="app">
  <app-nav></app-nav>
  <app-view>
    <app-sidebar></app-sidebar>
    <app-content></app-content>
  </app-view>
</div>
           
  • const

    常量

  • export

    檔案通過export暴露接口|變量

  • h => h(App)
// 演變步驟
render: function (createElement) {
    return createElement(App);
}
render (createElement) {
    return createElement(App);
}
render (h){
    return h(App);
}
           
It comes from the term “hyperscript”, which is commonly used in many virtual-dom implementations. “Hyperscript” itself stands for “script that generates HTML structures” because HTML is the acronym for “hyper-text markup language”. – by 尤雨溪
export default
  • ES6的export

    使用export指令定義了子產品的對外接口以後,其他JS檔案就可以通過import指令加載這個子產品(檔案),沒錯

$refs

持有所有被ref定義的元件

定義元件

  • 在不使用.vue 單檔案時,我們是通過 Vue 構造函數建立一個 Vue 根執行個體來啟動vuejs 項目,Vue 構造函數接受一個對象,這個對象有一些配置屬性 el, data, component, template 等,進而對整個應用提供支援。
  • new Vue()

    new Vue() 相當于一個構造函數,在入口檔案 main.js 構造根元件的同時,如果根元件還包含其它子元件,那麼 Vue 會通過引入的選項對象構造其對應的 Vue 執行個體,最終形成一棵

    元件樹

  • export default
  • 比較new Vue() & export default
  • 全局元件
Vue.component('todo-item', {
        template: '<li>這是個待辦項</li>'
    })
           
  • .vue檔案

    可以把html, css, js 寫到一個檔案中,進而實作了對一個元件的封裝

  • 父子關系

    在一個元件中通過 import 引入另一個元件,這個元件就是父元件,被引入的元件就是子元件.

    父元件通過props 向子元件傳遞資料,子元件通過自定義事件向父元件傳遞資料.

注冊元件

  • 全局注冊
Vue.component('component-a', { /* ... */ })
Vue.component('component-b', { /* ... */ })
Vue.component('component-c', { /* ... */ })
           
  • 局部注冊
new Vue({
  el: '#app',
  components: {
    'component-a': ComponentA,
    'component-b': ComponentB
  }
})
           

生命周期

computed
  • 計算屬性,如将總價在computed裡計算,進而實時計算商品購物車裡的商品總價
  • 對于任何複雜邏輯,你都應當使用計算屬性
  • 無變化時,緩存,提高效率;

文法

Object.assign

參數帶上這個,參數會被對象化,如果參數是數字或字元串,會被拆分成一個個的數字或字元;

工具

同步

Promise.all
  • 順序執行程序
// 假如有三個接口調用動作a1,a2,p5
Promise.all([a1,a2,p5]).then((result) => {
  console.log(result) // 結果數組或者錯誤
}).catch((error) => {
  console.log(error)
})
           
Promise.race

建構類

  • yarn

    可以代替npm的包依賴管理工具

VueX

  • 在SPA單頁面元件的開發中 Vue的vuex和React的Redux 都統稱為同一狀态管理,個人的了解是全局狀态管理更合适;簡單的了解就是你在state中定義了一個資料之後,你可以在所在項目中的任何一個元件裡進行擷取、進行修改,并且你的修改可以得到全局的響應變更.
  • 管理Token、全局個人偏好
mutations
const storeLogin = new Vuex.Store({

  state: {
    // 存儲token
    Authorization: localStorage.getItem('Authorization') ? localStorage.getItem('Authorization') : ''
  },
	
  mutations: {
    // 修改token,并将token存入localStorage
    changeLogin(state,user) {
      console.log('進入changeLogin')
      state.Authorization = user.Authorization;
      localStorage.setItem('Authorization', user.Authorization);
    }
  }
});
           

又如↓

mutations: {
  increment (state, payload) {
    state.count += payload.amount
  }
}
           

mutations下的函數隻适合接收一個對象參數,state是預設傳入,不能把state當做形參

rules

先在html引入rules

data裡定義rules

data() {
		loginRules: {
		// 驗證器 validateUsername 是一種特殊的函數
        username: [{ required: true, trigger: 'blur', validator: validateUsername }],
        password: [{ required: true, trigger: 'blur', validator: validatePassword }]
      },
  },
           

驗證器也在data裡面

const validateUsername = (rule, value, callback) => {
	// validUsername 是引入的js函數
      if (!validUsername(value)) {
        callback(new Error('請輸入正确的使用者名'))
      } else {
        callback()
      }
    }
           

引入validUsername

Element-ui

// 引入ui
import ElementUI from 'element-ui' //element-ui的全部元件
import 'element-ui/lib/theme-chalk/index.css'//element-ui的css
Vue.use(ElementUI) //使用elementUI
           
cnpm install [email protected] -S
           
穿梭框踩坑
  • 效果
    Vuejs學習v1.0.3,持續更新中。。。
<div style="text-align: left">
          <!-- <div style="text-align: center"> -->
          <el-transfer
            v-model="value4"
            style="text-align: left; display: inline-block"
            filterable
            :render-content="renderFunc"
            :titles="['使用者角色', '已選角色']"
            :button-texts="['放棄', '選擇']"
            :format="{
              noChecked: '${total}',
              hasChecked: '${checked}/${total}'
            }"
            :data="roleData"
            :props="defaultProps"
            @change="handleChange"
          >
<!--            ↓等效:render-content-->
<!--            <span slot-scope="{ option }">{{ option.roleId }} - {{ option.roleName }}</span>-->
            <el-button slot="left-footer" class="transfer-footer" size="small">操作</el-button>
            <el-button slot="right-footer" class="transfer-footer" size="small">操作</el-button>
          </el-transfer>
        </div>
           
data() {
    return {
      // 角色資料
      data: [],
      // 别名
      defaultProps: {
        key: 'roleId',
        label: 'roleName',
        disabled: false
      },
      // 不要value4無法移動元素
      value4: [1],
      renderFunc(h, option) {
      // 生成元素的顯示名稱
        return <span>{ option.roleName }</span>
        // return <span>{ option.roleId } - { option.roleName }</span>
      },
           
  • 目标框初始值不出來解決了
    Vuejs學習v1.0.3,持續更新中。。。

踩坑

Vuejs學習v1.0.3,持續更新中。。。
  • demo

    cc.vue

# 建立cc元件
template>
  <div>
      中國工農紅軍
      <ul>
          <li v-for="site in sites" :key="site.name">
              {{site.url}}
              <a :href="site.url" target="_blank">{{site.name}}</a>
          </li>
      </ul>
      <input type="button" value="點選我" @click="printText"/>
  </div>
</template>
<script>
// import { METHODS } from 'http'
export default {
  name: 'Cc',
  methods: {
    clickTest: function () {
      alert('你點選了按鈕')
    },
    printText: function () {
      console.log('你點選了按鈕')
    }
  },
  data () {
    return {
      msg: '書籍是人類進步的階梯',
      msg2: 'Apple',
      sites: [
        {url: 'http://router.vuejs.org/', name: 'Jack'},
        {url: 'http://vuex.vuejs.org/', name: 'Tom'},
        {url: 'https://github.com/vuejs/awesome-vue', name: 'Jimy'}
      ]
    }
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h1, h2 {
  font-weight: normal;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  display: inline-block;
  margin: 0 10px;
}
a {
  color: #42b983;
}
</style>

           

index.js

# 注入元件
import cc from '@/components/cc'
           

錯誤記錄

簡單

代碼不起效

  • 可能浏覽器緩存了,沒有更新,F12打開調試頁面,設定當打開F12時不緩存

Error in created hook: “TypeError: _admin.menuTree.then is not a function”

因為導入的函數沒加括弧

// 函數需要括弧,↓對的
menuTree().then(response => {
}
           

請求頭類型不對

// 元件内部增加下述代碼 - 局部請求頭
import Axios from 'axios'
Axios.defaults.headers.post['Content-Type'] = 'application/json'
# 或者在axios api裡加入
export function call(data) {
  return request({
    headers: {
      'Content-Type': 'application/json' // 設定請求頭請求格式為JSON
    },
    url: url,
    method: 'post',
    data
  })
}
           

‘vue-cli-service’ 不是内部或外部指令,也不是可運作的程式

檢查指令執行後,報錯:

'vue-cli-service' 不是内部或外部指令,也不是可運作的程式
           

解決辦法:将原

node_modules

重命名,重新執行

cnpm run dev

即可。是以

node_modules

最好是每個項目有一個個性化的,比如某些項目某些子產品必須

npm

安裝。

npm相關
  • js記憶體溢出
npm install -g increase-memory-limit
# 進入項目檔案夾運作:
increase-memory-limit
           
Webpack相關
  • 找不到子產品 Can’t find module
// cmpnt = () => import(`@/views${m_url}`)
cmpnt = (resolve) => require([`@/views${m_url}`], resolve)
// 上面的改成下面的
           

檢視原理

簡易功能開發

取消表單驗證提示

Vuejs學習v1.0.3,持續更新中。。。

再次打開,紅字依然存在,這個紅字是表單的,清除辦法:

this.$nextTick(() => {
        this.$refs.form.clearValidate() // 有效
      })
           

ajax

  • axios
# install axios
cnpm install axios --save-dev
# --save-dev以省掉手動修改package.json檔案的步驟
           

axios發送ajax請求

<script src="/js/axios.min.js"></script>
	window.onload=function(){
            new Vue({
                el:'#app',
                data:{
                    users:{
                        name:'',
                        age:''
                    }
                },
                methods:{
                    sendPsot(){
                        axios.post('post.php', {
                            name: this.users.name,
                            age: this.users.age,
                          })
                          .then(function (response) {
                            console.log(response);
                          })
                          .catch(function (error) {
                            console.log(error);
                          });
                    }
                    
                }
            });
        }
           
  • 跨域配置

    後端亦可配置跨域,前後端不要都配

    config/index.js

    ,proxyTable裡增加内容(

    老式

    )
proxyTable: {
      // 解決跨域
      '/tbapi':{
        // target: "http://api.douban.com/v2",
        target: "https://suggest.taobao.com",
        changeOrigin:true,
        pathRewrite:{
          '^/tbapi':''
        }
      }

    },
           

元件js

// 跨域
  Axios.defaults.baseURL = '/tbapi'
  Axios.defaults.headers.post['Content-Type'] = 'application/json'
           
mounted() {
      //GET
      this.$ajax({
        method: 'get',
        // tbapi會代替localhost
        url: '/sug?code=utf-8&q=電冰箱',
        // url: '/sug?code=utf-8&q=電冰箱&callback=cb',
      }).then(response => {
        // response包含config data等
        var resData = response.data.result
        iceBoxes = resData
        resData.forEach(item => {
          console.log(item[0])
          console.log(item[1])
          // console.log('資料序号'+i+'=='+item)
        })

      }).catch(function (err) {
        console.log(err)
      })

      //POST
      this.$ajax({
        method: 'post',
        url: '/sug?code=utf-8&q=iPhone',
        // data: {
        //   code: 'utf-8',
        //   q: 'iPhone'
        // }
      }).then(response => {
        // response包含config data等
        var resData = response.data.result
        resData.forEach(item => {
          console.log(item[0])
          console.log(item[1])
          // console.log('資料序号'+i+'=='+item)
        })
      }).catch(function (err) {
        console.log(err)
      });
    }
           

登入

邏輯
handleLogin() {
      this.$refs.loginForm.validate(valid => {
        if (valid) {
          this.loading = true
          // 也就是說,全局store對象擁有原生的dispatch方法,用于請求API
          this.$store.dispatch('user/login', this.loginForm).then(() => {
            this.$router.push({ path: this.redirect || '/' })
            this.loading = false
          }).catch(() => {
            this.loading = false
          })
        } else {
          console.log('error submit!!')
          return false
        }
      })
    }
           
源碼解析

上文的$store來自這裡

@/store/index.js

,片段↓

const store = new Vuex.Store({
  modules,
  getters
})
// 導出執行個體this的store屬性
export default store
           

store的dispatch,該單詞是發送的意思

axios的配置

// request即service-axios
import request from '@/utils/request'

// ↑request含有攔截器,url改為合适的baseUrl
export function login(data) {
  console.log('axios執行個體==',request)
  return request({
    url: '/user/login',
    method: 'post',
    data
  })
}
           

當index.vue裡的store.dispatch執行請求時,即會找到上面的login函數,由login函數發出調用請求,接着,我們看

request.js

裡的代碼

import axios from 'axios'
import { MessageBox, Message } from 'element-ui'
import store from '@/store'
import { getToken } from '@/utils/auth'
// global全局配置
import { baseUrl } from '@/utils/global'
// create an axios instance 沒錯,service就是axios執行個體,[email protected]/utils/request即注入service-axios執行個體
const service = axios.create({
  baseURL: baseUrl, // url = base url + request url
  // baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
  // withCredentials: true, // send cookies when cross-domain requests
  timeout: 5000 // request timeout
})
           

↑這是axios的配置,配置了url和逾時時間,當執行$store.dispatch時,即會加上baseUrl進行請求。

接收響應↓,也來自

request.js

response => {

    const res = response.data

    // if the custom code is not 20000, it is judged as an error.
    if (res.code !== 20000) {
      Message({
        message: res.message || 'Error',
        type: 'error',
        duration: 5 * 1000
      })

      // 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
      if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
        // to re-login
        MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
          confirmButtonText: 'Re-Login',
          cancelButtonText: 'Cancel',
          type: 'warning'
        }).then(() => {
          store.dispatch('user/resetToken').then(() => {
            location.reload()
          })
        })
      }
      return Promise.reject(new Error(res.message || 'Error'))
    } else {
      return res
    }
  },
           

背景API切換回Mock需改動

// 1 request.js
const service = axios.create({
  // baseURL: baseUrl, // url = base url + request url
  baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
  // withCredentials: true, // send cookies when cross-domain requests
  timeout: 5000 // request timeout
})
// 2 user.js
// params: data
    data
// 3 user.js
// method: 'post',
    method: 'get',
           

Token - 令牌

  • vuex的…mapMutations([’
  • 前後端屬于不同的域,導緻每次ajax請求伺服器都會當做新的使用者通路,導緻session丢失。當然也可以通過維護cookie來讓服務端辨識用戶端,如

    axios.defaults.withCredentials=true;

  • 每次請求被認為是新用戶端,産生新session問題,注意session的膨脹;

全局變量

引入模闆

彈窗
  • 建立模闆Test.vue
  • 使用
import TestMode from './Test' // 導入相對路徑的Test.vue
...
components: { TestMode }, // 注冊元件
           
test(row) { // 這裡是父元件
     this.testPageVisible = true
     this.$nextTick(() => {
       this.$refs.testMode2.test(Object.assign({}, row)) // 調用子元件的函數 testMode2
     })
   },
           
methods: {
    test() { // 這裡是Test.vue元件·················
      this.dialogFormVisible = true
    }
  }
           

編輯器

Json編輯器

原版↓很糟糕

Vuejs學習v1.0.3,持續更新中。。。

使用json元件解決,待續

布局

九宮格|十六宮格

事态緊急跨域用表格代替九宮格,不過不正規。

正規的待續

相容

解決IE相容問題

  • promise
# 安裝es6-promise
npm install es6-promise --save-dev
           
  • main.js中引入ES6的polyfill
import Es6Promise from 'es6-promise'
Es6Promise.polyfill();
           

上文安裝的

promise

隻是針對性的,要徹底相容IE還需要研究。

特殊代碼

cookie

# Vue官方的localStorage
Vue.ls.get(key)
           

繼續閱讀