Fork me on GitHub

mvvm

mvvm 框架

1.了解

Vue React Angular

MVC Model View Controller
MVVM Model View ViewModel

mvvm定位 Model <=> ViewModel <=> View Model 与 View 是分离的

MVC 与 MVVM 区别

在MVC里,View是可以直接访问Model的。Model不依赖View,而View依赖于Model。更改view更难。
在MVVM里, View(页面)与Model(数据)是分离的。

2.双向绑定的原理

  1. data=>view data改变会使view变化
    view=>data view变化也会改变data
    绑定 => 自动化处理,不需要人为关心。
  2. 原理
    data => view Object.defineProperty get/set操作 依靠它 监听data的变化
    view => data input事件,只不过框架执行了我们看不到。

    1
    2
    3
    4
    5
    6
    var obj = { };
    // 为obj定义一个名为 hello 的访问器属性
    Object.defineProperty(obj, "hello", {
    get: function () {return sth},
    set: function (val) {/* do sth */}
    })

    object.defineProperty 与 reflect.defineProperty 区别

    object.defineProperty 返回的是一个对象
    reflect.defineProperty 返回Boolean值作为成功的状态
    

    object.defineProperty 还可以用来 深度复制
    let desc = Object.getOwnPropertyDescriptor(source,key);
    Object.defineProperty(target,key,desc);

3.设计模式

观察者模式 data => Observer Dep Watcher => View

4.生命周期

Vue
beforeCreate  => el与data并未初始化        => 可以在这加loading事件。
created          => 完成了data数据的初始化。  => 做一些初始化,实现函数自执行。
beforeMount   => 完成了el和data初始化
mounted          => 完成挂载                   => 向后端发起请求,那回数据,配合路由钩子做事
beforeDestory
destoryed                                   => 当前组件已被删除,清空相关内容。

beforeUpdate
updated
React
static defaultProps
constructor            => 接收父组件的props,context

componentWillMount        => 组件刚经历constructor,初始完数据 组件还未render,dom未渲染
render
componentDidMount      => 组件第一次渲染完成,此时dom节点已经生成,可以调用ajax,返回数据setState后组件会重新渲染
componentWillUnmount   => clear 组件中的定时器 setTimeout,setInterval,移除组件监听。

componentWillReceiveProps
shouldCompoentUpdate
componentWillUpdate
render
componentDidUpdate

5. 路由

vue-router
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import VRouter from 'vue-router';
app.use(VRouter);
let router = new VueRouter({
mode: 'history',
routes: [
{path:'/',component: IndexPage},
{path:'/detail',component: DetailPage,redirect:'/detail/analysis',
children: [
{path:'forecast',component: ForPage},
{path:'analysis',component: AnaPage},
{path:'count',component: CouPage},
{path:'publish',component: PubPage}
]
}
]
})
new vue({router})
vue 比 react 多了一个 router-view
1
2
3
<keep-alive>
<router-view :seller="seller"></router-view>
</keep-alive>
keep-alive 作用 把切换出去的组件保存到内存中,保留它的状态或避免重复渲染。
链接(重定向)

router-link

  1. 就变成li标签了
react-router-dom
1
2
3
4
5
6
7
8
9
import {Route,BrowserRouter,Switch,HashRouter} from 'react-router-dom';
<HashRouter>
<Switch>
<Route exact path="/" component={PCIndex}></Route>
<Route path="/details/:uniquekey" component={PCNewsDetails}></Route>
<Route path="/usercenter" component={PCUserCenter}></Route>
</Switch>
</HashRouter>
链接(重定向)

import {Link} from ‘react-router-dom’;

我的私人音乐坊 &qt;

6. params

vue
获取params => this.$route.params
react
获取params => this.props.params

7. 获取DOM

1.vue=> this.$ref.name
2.react => this.ref.name

8. 数据请求

vue
vue-resource
import VueResource from 'vue-resource';

app.use(VueResource);

this.$http.get('/goods/list')
    .then((response) => {
        response = response.body;
            if (response.status === '0') {
                 this.goods = response.result.list;
                       this.$nextTick(() => {
                     this._initScroll();
                     this._calculateHeight();
                });
            }
    });    

跨域:代理 proxyTable
axios => vue2.0以后用axios
import axios from 'axios';

axios.get('/users/addressList').then((response)=>{
        var res = response.data;
        this.addressList = res.result;
        this.addressList.forEach((item,index)=>{
           if(item.isDefault){
             this.currentIndex = index;
             this.selectedAddressId = item.addressId;
           }
        })
      })

跨域
跨域post实例,用到了qs组件来避开ajax信使请求,并兼容Android。
import axios from 'axios';
import qs from 'qs';

axios.post('http://www.xyz.com/request', qs.stringify(params))
.then(response => {
  console.log(response);
})
.catch(err => {
  console.log(err);
});
react => fetch
var myFetchOptions = {
    method: 'GET'
};
fetch("http://newsapi.gugujiankong.com/Handler.ashx?action=uc&userid=" + localStorage.React_userid + "&uniquekey=" + this.props.uniquekey, myFetchOptions)
.then(response=>response.json())
.then(json=>{
    //收藏成功以后进行一下全局的提醒
    notification['success']({message:'ReactNews提醒',description:'收藏此文章成功'});
})

fetch 跨域 => 数据模拟我选择代理方式proxy || fetch-jsonp 插件实现jsonp
"proxy": {
  "/api": {
    "target": "http://localhost:4000",
    "secure": false
  },
  "/tuan": {
    "target": "http://m.dianping.com",
    "secure": false
  }
}

9. 组件间数据传递

vue
父子组件

父 => 子

父组件中<food :food="selectedFood"></food>
子组件接收
    props:{
        food:{
            type:Object
        }
    }
子组件使用数据方式 this.food (即与data中数据使用方式相同)

子 => 父

this.$emit('add',target/msg) => 触发父组件中add事件,执行add绑定的函数。
子组件之间数据传递 eventBus or vuex全局
 evetnBus

功能:实现组件间通信,点击外部关闭select=>同一时间最多有一个select处于下拉状态
建立eventBus.js
  import Vue from 'vue'
  const eventBus = new Vue()
  export { eventBus }
全局组件中定义click事件 
  resetComponent() { eventBus.$emit('reset-component') }
具有select的组件中触发eventBus 
  mounted () {
       eventBus.$on('reset-component', () => {
         this.isDrop = false
       })
       // 每一个select组件 只要触发reset-component 就会使this.isDrop重置
  }
  toggleDrop(e) {
    e.stopPropagation()  //注意要阻止冒泡
    eventBus.$emit('reset-component')  //多个select 点击别的select也触发事件
    this.isDrop = !this.isDrop 
  }

vuex

核心 State Getter Mutation Action Module

actions(commit) => mutations(同步函数) => state(改变state) => components(自动更新componets)

1.import Vuex from 'vuex';
2. Vue.use(Vuex);
3. const store = new Vuex.Store({
    state:{
        userName:'',
        cartCount:0
    },
    mutations:{
        updateUserInfo(state,userName){
            state.userName = userName;
        },
        updateCartCount(state,cartCount){
            state.cartCount += cartCount;
        },
        initCartCount(state,cartCount){
            state.cartCount = cartCount;
        }
    }
})
4.new Vue({store});

const store = new Vuex.Store({
    state:{
        userName:'',
        cartCount:0
    },
    mutations:{
        updateUserInfo(state,userName){
            state.userName = userName;
        },
        updateCartCount(state,cartCount){
            state.cartCount += cartCount;
        },
        initCartCount(state,cartCount){
            state.cartCount = cartCount;
        }
    }
})
<!-- 在各个组件中都可以使用 this.$store.commit(mutations中的方法) 来更新components -->
this.$store.commit("updateCartCount",-1);

Vuex 并不限制你的代码结构。但是,它规定了一些需要遵守的规则:
       应用层级的状态应该集中到单个 store 对象中。
       提交 mutation 是更改状态的唯一方法,并且这个过程是同步的。
       异步逻辑都应该封装到 action 里面。
State  作为一个“唯一数据源 (SSOT)”而存在
       从 store 实例中读取状态最简单的方法就是在计算属性中返回某个状态:
       computed: {
           count () {
             return store.state.count
           }
       } 
       mapState: mapGetters...同理
         import { mapState } from 'vuex'
         ...mapState(...)  
Getter  有时候我们需要从 store 中的 state 中派生出一些状态,例如对列表进行过滤并计数:
        const store = new Vuex.Store({
          state: {
            todos: [
              { id: 1, text: '...', done: true },
              { id: 2, text: '...', done: false }
            ]
          },
          getters: {
            doneTodos: state => {
              return state.todos.filter(todo => todo.done)
            }
          }
        })
Mutation 更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。
         触发 store.commit('increment')
         注意事项
           最好提前在你的 store 中初始化好所有所需属性。
           当需要在对象上添加新属性时,你应该
           使用 Vue.set(obj, 'newProp', 123), 或者
           以新对象替换老对象。例如,利用 stage-3 的对象展开运算符我们可以这样写:
           state.obj = { ...state.obj, newProp: 123 }

           一条重要的原则就是要记住 mutation 必须是同步函数
Action    Action 类似于 mutation,不同在于:
            Action 提交的是 mutation,而不是直接变更状态。
            Action 可以包含任意异步操作。
          Action 通过 store.commit 方法触发:
            store.commit('increment')
react
父子组件数据传递
  1. 父 => 子 通过props传递

父组件
子组件接收 this.props.food

vue 与 react 数据使用格式区别
vue
1.html中=>直接使用 data/porps中数据名称。
2.js中=> this.数据名称
react
1.html/js中 => this.props.数据名称/this.state.数据名称

  1. 子 => 父 通过函数 父组件中函数传递给子组件,子组件中返回数据给父组件
1
2
3
4
5
6
7
8
9
10
11
12
子组件 progress组件中:
changeProgress(e){
let progressBar = this.refs.progressBar;
let progress = (e.clientX - progressBar.getBoundingClientRect().left) /progressBar.clientWidth;
this.props.onProgressChange && this.props.onProgressChange(progress);
}
<div className="components-progress" ref="progressBar" onClick={this.changeProgress.bind(this)}>
父组件 player组件中:
progressChangeHandler(progress) {
$('#player').jPlayer(this.state.isPlay?'play':'pause', duration*progress);
}
<Progress progress={this.state.progress} onProgressChange={this.progressChangeHandler.bind(this)} />
  1. 子组件间通信

1.子 => 父 => 子

子组件传给父组件 父组件根据子组件数据改变state, state改变重新render下面的子组件
由于 Parent 的 state 发生变化,会触发 Parent 及从属于 Parent 的子组件的生命周期,所以我们在控制台中可以看到,在各个组件中的 componentDidUpdate 方法均被触发。所以有别的就用别的。

2.eventProxy

eventProxy 中,总共有 on、one、off、trigger 这 4 个函数:

on、one:on 与 one 函数用于订阅者监听相应的事件,并将事件响应时的函数作为参数,on 与 one 的唯一区别就是,使用 one 进行订阅的函数,只会触发一次,而 使用 on 进行订阅的函数,每次事件发生相应时都会被触发。
trigger:trigger 用于发布者发布事件,将除第一参数(事件名)的其他参数,作为新的参数,触发使用 one 与 on 进行订阅的函数。
off:用于解除所有订阅了某个事件的所有函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
'use strict';
const eventProxy = {
onObj: {},
oneObj: {},
on: function(key, fn) {
if(this.onObj[key] === undefined) {
this.onObj[key] = [];
}
this.onObj[key].push(fn);
},
one: function(key, fn) {
if(this.oneObj[key] === undefined) {
this.oneObj[key] = [];
}
this.oneObj[key].push(fn);
},
off: function(key) {
this.onObj[key] = [];
this.oneObj[key] = [];
},
trigger: function() {
let key, args;
if(arguments.length == 0) {
return false;
}
key = arguments[0];
args = [].concat(Array.prototype.slice.call(arguments, 1));
if(this.onObj[key] !== undefined
&& this.onObj[key].length > 0) {
for(let i in this.onObj[key]) {
this.onObj[key][i].apply(null, args);
}
}
if(this.oneObj[key] !== undefined
&& this.oneObj[key].length > 0) {
for(let i in this.oneObj[key]) {
this.oneObj[key][i].apply(null, args);
this.oneObj[key][i] = undefined;
}
this.oneObj[key] = [];
}
}
};
export default eventProxy;

栗子 => usercenter页面改变数据 更新header 登录状态;trigger =>触发 on事件

header => 注册on事件

1
2
3
4
5
6
7
import eventProxy from '../eventProxy'
eventProxy.on('hasLogined', (hasLogined) => {
this.setState({
hasLogined
});
});
}

usercenter => 触发 trigger

1
2
3
4
5
6
import eventProxy from '../eventProxy'
logout(){
localStorage.React_userid = '';
localStorage.userNickName = '';
eventProxy.trigger('hasLogined', false);
}

  1. redux
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import { createStore } from 'redux';
/**
* 这是一个 reducer,形式为 (state, action) => state 的纯函数。
* 描述了 action 如何把 state 转变成下一个 state。
*
* state 的形式取决于你,可以是基本类型、数组、对象、
* 甚至是 Immutable.js 生成的数据结构。惟一的要点是
* 当 state 变化时需要返回全新的对象,而不是修改传入的参数。
*
* 下面例子使用 `switch` 语句和字符串来做判断,但你可以写帮助类(helper)
* 根据不同的约定(如方法映射)来判断,只要适用你的项目即可。
*/
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
}
// 创建 Redux store 来存放应用的状态。
// API 是 { subscribe, dispatch, getState }。
let store = createStore(counter);
// 可以手动订阅更新,也可以事件绑定到视图层。
store.subscribe(() =>
console.log(store.getState())
);
// 改变内部 state 惟一方法是 dispatch 一个 action。
// action 可以被序列化,用日记记录和储存下来,后期还可以以回放的方式执行
store.dispatch({ type: 'INCREMENT' });
// 1
store.dispatch({ type: 'INCREMENT' });
// 2
store.dispatch({ type: 'DECREMENT' });
// 1

Provider connect

Provider 内的组件要必须被 connect 过的

connect => mapStateToProps(state,ownProps)/mapDispatchToProps(dispatch,ownProps)

-------------本文结束感谢您的阅读-------------