1. 认识Vue

192 阅读6分钟

一、渐进式框架、三大框架对比、数据流和数据绑定

1.渐进式框架

Vue对自己框架和其他框架对比后,生产的一个特定的名词 progresive framework,只关注视图层

  1. Angular - 综合性框架开发平台,更关注项目应用,适合开发大型应用
    • 不是只解决一个视图渲染或状态的管理,而是先有了需求,然后全部集成在angular框架内部,需要什么就提供什么
    • 这是一种自上而下的设计
  2. React - 只关注用户界面(View视图层)
    • 怎么把数据渲染到视图中
    • 自己不是框架,称之为库,不提供状态中央管理(Redux)、路由(react-router),依靠第三方,有学习成本
  3. Vue - 只关注用户界面(View视图层)
    • 怎么把数据渲染到视图中 - 核心库
    • Vuex、vue-router 可以选择集成,不像Angular一样原本是集成的
    • React和vue共同点:只关注视图,在底层写一个驱动出来,让用户不操作DOM,不关心数据怎么渲染,自下往上开发

2.三大框架对比

vue和React共同点: 只关注视图,在底层写一个驱动出来,让用户不操作DOM,不关心数据怎么渲染,自下往上开发,angular是自上而下
vue和Angular共同点: 有强规范,一定要按照这种指定的规范开发,React没有,不是组件就是一个类,编写视图用jsx,或者react元素,比较灵活

3.数据流和数据绑定

  1. 数据绑定:数据与视图渲染之间的关系

    • React:单向数据绑定 event事件触发 -> state更改 -> 视图变更
    • Vue:双向数据绑定
      • event事件触发 -> state/data更改 -> 视图变更
      • v-model -> 视图变化 -> state/data变更
  2. 数据流:数据流淌的方向 -> 父子组件中 数据按照什么方向流动

    • React 和 Vue 都是单向数据流
    • 父组件传递state -> 子组件作为props
    • 子组件把props变更 -> 父组件state变更 子组件不可以更改props
    • 父组件state变更 -> 子组件props变更 父组件更新,子组件必定更新
      props: immutable value 不可变的值
      state/data: mutable value 可变的值

二、vue的几种构建方式

1. vite + cdn

image.png

  1. npm init -y 创建package.json文件
"scripts": {
    "dev": "vite"
}
  1. yarn add vite -D
  2. index.html 引入cdn 及 main.js
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  
  <div id="app"></div>

  <script src="https://unpkg.com/vue@3.1.2/dist/vue.global.js"></script>
  <script type="module" src="./src/main.js"></script>
</body>
</html>

4.1 src/main.js vue2写法

const { createApp } = Vue;

const App = {
  data() {
    return {
      text: 'Hello Vue!!'
    }
  },
  template: ` 
    <div>
      <h1>{{ text }}</h1>
      <button @click="change">Change</button>
    </div>
  `,
  methods: {
    change() {
      this.text = 'Hello Vite!!';
    }
  }
}

createApp(App).mount('#app');

4.2 vue3写法

const { createApp, ref } = Vue;

const App = {
  template: ` 
    <div>
      <h1>{{ text }}</h1>
      <button @click="change">Change</button>
    </div>
  `,
  setup() {
    const text = ref('Hello Vue!!');

    const change = () => {
      text.value = 'Hello Vite!!'
    }

    return {
      text,
      change
    }
  }
}

createApp(App).mount('#app');
  1. npm run dev启动服务

image.png image.png

2. 使用vite创建项目

image.png

npm init vue@latest
cd <your-project-name>
npm install 
npm run dev

image.png

3. vue-cli创建项目

yarn global add @vue/cli 
vue create project-name
cd project-name
yarn serve

三、Webpack从0构建Vue2 vue3项目

vue2步骤:

npm init -y
"scripts": {
    "dev": "webpack-dev-server"
}

index.html 引入vue2 cdn
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>

npm i webpack@4.44.2 webpack-cli@3.3.12 webpack-dev-server@3.11.2 -D
yarn add vue-loader@15.9.7 vue-template-compiler@2.6.14 html-webpack-plugin@4.5.0 -D
App.vue
<template>
  <div>{{ title }}</div>
</template>

<script>
export default {
  name: 'App',
  data(){
    return {
      title: "Hello Vue!!!"
    }
  }
}
</script>
main.js
import App from './App.vue'

new Vue({
  render: h => h(App)
}).$mount('#app');
webpack.config.js
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin');

module.exports = {
  mode: 'development',
  entry: './src/main.js',
  output: {
    path: resolve(__dirname, 'dist'),
    filename: 'main.js'
  },
  // 引用外部文件cdn
  externals: {
    'vue': 'Vue'
  },
  devtool: 'source-map',
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      }
    ]
  },
  plugins: [
    new VueLoaderPlugin(),
    new HtmlWebpackPlugin({
      template: resolve(__dirname, 'public/index.html')
    })
  ]
};

vue3步骤:

index.html 引入vue3 cdn
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>

npm i @vue/compiler-sfc -D
npm i vue-loader@16.2.0 -D

webpack.config.js
const { VueLoaderPlugin } = require('vue-loader');

main.js
import App from './App.vue';
Vue.createApp(App).mount('#app');

四、应用实例、组件实例与根组件实例

1. 应用实例

/**
 * 应用实例
 * createApp 创建APP 返回一个应用实例
 * 应用实例主要是用来注册全局组件
 * 
 * 
 **/ 
// Application 应用
const app = Vue.createApp({});

/**
 * 实例上暴露了很多方法
 * component 注册组件
 * directive 注册指令
 * filter    注册过滤器
 * use       使用插件
 * 
 * 大多数这样的方法都会返回createApp创建出来的应用实例
 * 允许链式操作
 * console.log(app2 === app);// true
 */
// 返回原本的应用实例
const app2 = app.component('MyTitle', {
  data(){
    return {
      title: 'I LOVE VUE!!'
    }
  },
  template: `<h1 v-to-lower-case> {{ title }} </h1>`
}).directive('toLowerCase', {
  mounted (el){// el是h1
    el.addEventListener('click', function(){
      this.innerText = this.innerText.toLowerCase();
    }, false);
  }
}).mount('#app');
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  
  <div id="app">
    <my-title></my-title>
  </div>

  <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
</body>
</html>

2. 根组件实例

/**
 * 根组件的本质就是一个对象 {}
 * createApp执行的时候需要一个根组件 createApp({})
 * 根组件是Vue渲染的起点
 * 
 * 
 * 根元素是一个HTML元素
 * createApp执行创建Vue应用实例时,需要一个HTML根元素
 * <div id="app"></div>
 */
 const RootComponent = {
  data(){
    return {
      a: 1,
      b: 2,
      total: 0
    }
  },
  mounted(){
    this.plus();
  },
  methods: {
    plus(){
      this.total = this.a + this.b;
    }
  },
  template: `<h1>{{ a }}+{{ b }} = {{ total }}</h1>`
}

const app = Vue.createApp(RootComponent);

/**
 * mount方法执行返回的是根组件实例
 * vm -> ViewModel -> MVVM -> VM
 * Vue 不是一个完整的MVVM模型
 */

const vm = app.mount('#app');
console.log(vm.a, vm.b, vm.total);// 根组件实例
console.log(app);// app里面没有这些属性

3. 组件实例

/**
 * 每个组件都有自己的组件实例
 * 一个应用中所有的组件都共享一个应用实例
 * 无论是根组件还是应用内其他的组件配置选项、组件行为都是一样的
 * 
 * 组件实例可以添加一些属性 
 *  + data/props/components/methods...
 *  + this -> $attrs/$emit Vue组件实例内置方法
 */
const MyTitle = {
  template: `
    <h1>
      <slot></slot>
    </h1>
  `
};

const MyAuthor = {
  template: `
    <p>
      Author: <slot></slot>
    </p>
  `
};

const MyContent = {
  template: `
    <p><slot></slot></p>
  `
};

const App = {
  compoents: {
    /** title author content */
    MyTitle,
    MyAuthor,
    MyContent
  },
  data(){
    return {
      title: 'This is a TITLE',
      author: 'Xiaohe',
      content: 'This is a CONTENT'
    }
  },
  template: `
    <div>
      <my-title>{{ title }}</my-title><br>
      <my-author>Author: {{ author }}</my-author><br>
      <my-content>{{ content }}</my-content>
    </div>
  `
};

const app = Vue.createApp(App);

const vm = app.mount('#app');

console.log(vm);

五、认识以及实现MVC

M: Model 数据模型(模型层) -> 操作数据库 (对数据进行增删改查的操作)
V: View 视图层 -> 显示视图或视图模板
C: Controller 控制器层 -> 逻辑层 数据和视图关联挂载和基本的逻辑操作

服务端渲染 view -> controller -> model

graph TD
View需要数据 --> Controller对应的方法 --> 调用Model的方法 --> 获取数据 --> 返回给Controller对应的方法 --> render到View中

前端渲染 controller -> model -> view

graph TD
前端 --> 异步请求URL=API层--> 对应控制器中的一个方法--> 调用Model层的方法--> 操作数据库--> 获取数据--> 返回给控制器方法--> 响应回前端

前端MVC
Model:管理视图所需要的数据 (数据与视图的关联)
View:HTML模板 + 视图渲染
Controller:管理事件逻辑

MVVM模型雏形: ViewModel做底层驱动,M data, V view
vue: 关注于视图渲染,但是ref可以操作DOM节点,违背了MVVM的初衷,MVVM写ViewModel的目的是为了隔离 M 和 V
vue的核心是一个视图渲染库,只管理视图的绑定更新渲染,在后续加Vuex和vue-router微型库渐进式的组成一个框架

实现加减乘除计算器

image.png

Model -> data -> a b s r
        watch -> data change -> update view
view -> template -> render
controller -> event trigger -> model/data
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  
  <div id="app"></div>

  <script src="./mvc.js"></script>

</body>
</html>
//mvc.js
(function () {

  function init() {
    model.init(); // 组织数据 + 数据监听操作 / 数据代理
    view.render(); // 组织HTML模板 + 渲染HTML模板
    controller.init(); // 事件处理函数定义与绑定
  }

  var model = {
    data: {
      a: 0,
      b: 0,
      s: '+',
      r: 0
    },
    init: function () {
      var _this = this;

      for (var k in _this.data) {
        (function (k) {
          Object.defineProperty(_this, k, {
            get: function () {
              // model.a -> get
              return _this.data[k];
            },
            set: function (newValue) {
              // model.a = 123; -> set
              _this.data[k] = newValue;
              view.render({ [k]: newValue });
            }
          })
        })(k)
      }
    }
  }

  // calculator
  var view = {
    el: '#app',
    template: `
      <p>
        <span class="cal-a">{{ a }}</span>
        <span class="cal-s">{{ s }}</span>
        <span class="cal-b">{{ b }}</span>
        <span>=</span>
        <span class="cal-r">{{ r }}</span>
      </p>
      <p>
        <input type="text" placeholder="Number a" class="cal-input a" />
        <input type="text" placeholder="Number b" class="cal-input b" />
      </p>
      <p>
        <button class="cal-btn">+</button>
        <button class="cal-btn">-</button>
        <button class="cal-btn">*</button>
        <button class="cal-btn">/</button>
      </p>
    `,
    render: function (mutedData) {
      if (!mutedData) {
        this.template = this.template.replace(
          /\{\{(.*?)\}\}/g,
          function (node, key) {
            // node: {{ a }} key: a
            return model[key.trim()];
          }
        )

        var container = document.createElement('div');
        container.innerHTML = this.template;
        document.querySelector(this.el).appendChild(container);
      } else {
        for (var k in mutedData) {
          document.querySelector('.cal-' + k).textContent = mutedData[k];
        }
      }
    }
  }

  var controller = {
    init: function () {
      var oCalInputs = document.querySelectorAll('.cal-input'),
        oCalBtns = document.querySelectorAll('.cal-btn'),
        inputItem,
        btnItem;

      for (var i = 0; i < oCalInputs.length; i++) {
        inputItem = oCalInputs[i];

        inputItem.addEventListener('input', this.handleInput, false);
      }

      for (var i = 0; i < oCalBtns.length; i++) {
        btnItem = oCalBtns[i];

        btnItem.addEventListener('click', this.handleBtnClick, false);
      }
    },
    handleInput: function (e) {
      var tar = e.target,
        value = Number(tar.value),
        field = tar.className.split(' ')[1];

      model[field] = value;

      // model.r = eval('model.a' + model.s + 'model.b')
      with (model) {
        r = eval('a' + s + 'b');
      }
    },
    handleBtnClick: function (e) {
      var type = e.target.textContent;

      model.s = type;

      with (model) {
        r = eval('a' + s + 'b');
      }
    }
  }

  init();
})();