一、渐进式框架、三大框架对比、数据流和数据绑定
1.渐进式框架
Vue对自己框架和其他框架对比后,生产的一个特定的名词 progresive framework,只关注视图层
- Angular - 综合性框架开发平台,更关注项目应用,适合开发大型应用
- 不是只解决一个视图渲染或状态的管理,而是先有了需求,然后全部集成在angular框架内部,需要什么就提供什么
- 这是一种自上而下的设计
- React - 只关注用户界面(View视图层)
- 怎么把数据渲染到视图中
- 自己不是框架,称之为库,不提供状态中央管理(Redux)、路由(react-router),依靠第三方,有学习成本
- Vue - 只关注用户界面(View视图层)
- 怎么把数据渲染到视图中 - 核心库
- Vuex、vue-router 可以选择集成,不像Angular一样原本是集成的
- React和vue共同点:只关注视图,在底层写一个驱动出来,让用户不操作DOM,不关心数据怎么渲染,自下往上开发
2.三大框架对比
vue和React共同点: 只关注视图,在底层写一个驱动出来,让用户不操作DOM,不关心数据怎么渲染,自下往上开发,angular是自上而下
vue和Angular共同点: 有强规范,一定要按照这种指定的规范开发,React没有,不是组件就是一个类,编写视图用jsx,或者react元素,比较灵活
3.数据流和数据绑定
-
数据绑定:数据与视图渲染之间的关系
- React:单向数据绑定 event事件触发 -> state更改 -> 视图变更
- Vue:双向数据绑定
- event事件触发 -> state/data更改 -> 视图变更
- v-model -> 视图变化 -> state/data变更
-
数据流:数据流淌的方向 -> 父子组件中 数据按照什么方向流动
- React 和 Vue 都是单向数据流
- 父组件传递state -> 子组件作为props
- 子组件把props变更 -> 父组件state变更 子组件不可以更改props
- 父组件state变更 -> 子组件props变更 父组件更新,子组件必定更新
props: immutable value 不可变的值
state/data: mutable value 可变的值
二、vue的几种构建方式
1. vite + cdn
npm init -y创建package.json文件
"scripts": {
"dev": "vite"
}
yarn add vite -D- 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');
npm run dev启动服务
2. 使用vite创建项目
npm init vue@latest
cd <your-project-name>
npm install
npm run dev
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微型库渐进式的组成一个框架
实现加减乘除计算器
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();
})();