Vuejs學習,持續更新中。。。

基礎知識

爲什麼要學習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上,Vue作者尤雨溪正式宣佈加盟阿里巴巴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

組件

概念

  • 組件類似自定義元素.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裏計算,從而實時計算商品購物車裏的商品總價
  • 對於任何複雜邏輯,你都應當使用計算屬性
  • 無變化時,緩存,提高效率;

工具

構建類

  • 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

<el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form" autocomplete="on" label-position="left">

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

import { validUsername } from '@/utils/validate' // 導入js

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
穿梭框踩坑
  • 效果
    在這裏插入圖片描述
<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>
      },
  • 目標框初始值不出來解決了
    在這裏插入圖片描述

功能開發

*

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

錯誤記錄

簡單
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
  })
}

功能

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實例,import@/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-mode v-if="testPageVisible" ref="testMode2" @refreshDataList="getList"></test-mode><!-- 使用組件.getList是確定後的執行函數-->
test(row) { // 這裏是父組件
     this.testPageVisible = true
     this.$nextTick(() => {
       this.$refs.testMode2.test(Object.assign({}, row)) // 調用子組件的函數
     })
   },
methods: {
    test() { // 這裏是Test.vue組件·················
      this.dialogFormVisible = true
    }
  }

編輯器

Json編輯器

原版↓很糟糕
在這裏插入圖片描述

兼容

解決IE兼容問題

  • promise
# 安裝es6-promise
npm install es6-promise --save-dev
  • main.js中引入ES6的polyfill
import Es6Promise from 'es6-promise'
Es6Promise.polyfill();
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章