什么是MVC?
MVC的全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,是一种软件设计典范。
V即View视图是指用户看到并与之交互的界面。
M即Model模型是管理数据 ,很多业务逻辑都在模型中完成。在MVC的三个部件中,模型拥有最多的处理任务。
C即Controller控制器是指控制器接受用户的输入并调用模型和视图去完成用户的需求,控制器本身不输出任何东西和做任何处理。它只是接收请求并决定调用哪个模型构件去处理请求,然后再确定用哪个视图来显示返回的数据。
MVC只是看起来很美
MVC框架的数据流很理想,请求先到Controller, 由Controller调用Model中的数据交给View进行渲染,但是在实际的项目中,又是允许Model和View直接通信的。
在2013年,Facebook让React亮相的同时推出了Flux框架,React的初衷实际上是用来替代jQuery的,Flux实际上就可以用来替代Backbone.js,Ember.js等一系列MVC架构的前端JS框架。
其实Flux在React里的应用就类似于Vue中的Vuex的作用,但是在Vue中,Vue是完整的mvvm框架,而Vuex只是一个全局的插件。
React只是一个MVC中的V(视图层),只管页面中的渲染,一旦有数据管理的时候,React本身的能力就不足以支撑复杂组件结构的项目,在传统的MVC中,就需要用到Model和Controller。Facebook对于当时世面上的MVC框架并不满意,于是就有了Flux, 但Flux并不是一个MVC框架,他是一种新的思想。
Flux的流程:
React 只是 DOM 的一个抽象层,并不是 Web 应用的完整解决方案。有两个方面,它没涉及。
2013年 Facebook 提出了 Flux 架构的思想,引发了很多的实现。2015年,Redux 出现,将 Flux 与函数式编程结合一起,很短时间内就成为了最热门的前端架构。
如果你不知道是否需要 Redux,那就是不需要它
只有遇到 React 实在解决不了的问题,你才需要 Redux
简单说,如果你的UI层非常简单,没有很多互动,Redux 就是不必要的,用了反而增加复杂性。
需要使用Redux的项目:
从组件层面考虑,什么样子的需要Redux:
Redux的设计思想:
注意:flux、redux都不是必须和react搭配使用的,因为flux和redux是完整的架构,在学习react的时候,只是将react的组件作为redux中的视图层去使用了。
Redux的使用的三大原则:
这个部分,不使用react,直接使用原生的html/js来写一个简易的的redux
基本的状态管理及数据渲染:
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <meta http-equiv="X-UA-Compatible" content="ie=edge">
- <title>Redux principle 01</title>
- </head>
- <body>
- <h1>redux principle</h1>
- <div class="counter">
- <span class="btn" onclick="dispatch({type: 'COUNT_DECREMENT', number: 10})">-</span>
- <span class="count" id="count"></span>
- <span class="btn" id="add" onclick="dispatch({type: 'COUNT_INCREMENT', number: 10})">+</span>
- </div>
- <script>
- // 定义一个计数器的状态
- const countState = {
- count: 10
- }
-
- // 定一个方法叫changeState,用于处理state的数据,每次都返回一个新的状态
- const changeState = (action) => {
- switch(action.type) {
- // 处理减
- case 'COUNT_DECREMENT':
- countState.count -= action.number
- break;
- // 处理加
- case 'COUNT_INCREMENT':
- countState.count += action.number
- break;
- default:
- break;
- }
- }
-
- // 定义一个方法用于渲染计数器的dom
- const renderCount = (state) => {
- const countDom = document.querySelector('#count')
- countDom.innerHTML = state.count
- }
-
- // 首次渲染数据
- renderCount(countState)
-
- // 定义一个dispatch的方法,接收到动作之后,自动调用
- const dispatch = (action) => {
- changeState(action)
- renderCount(countState)
- }
-
- </script>
- </body>
- </html>
-
创建createStore方法
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <meta http-equiv="X-UA-Compatible" content="ie=edge">
- <title>Redux principle 02</title>
- </head>
- <body>
- <h1>redux principle</h1>
- <div class="counter">
- <span class="btn" onclick="store.dispatch({type: 'COUNT_DECREMENT', number: 10})">-</span>
- <span class="count" id="count"></span>
- <span class="btn" id="add" onclick="store.dispatch({type: 'COUNT_INCREMENT', number: 10})">+</span>
- </div>
- <script>
- // 定义一个方法,用于集中管理state和dispatch
- const createStore = (state, changeState) => {
- // getState用于获取状态
- const getState = () => state
-
- // 定义一个监听器,用于管理一些方法
- const listeners = []
- const subscribe = (listener) => listeners.push(listener)
-
- // 定义一个dispatch方法,让每次有action传入的时候返回render执行之后的结果
- const dispatch = (action) => {
- // 调用changeState来处理数据
- changeState(state, action)
- // 让监听器里的所以方法运行
- listeners.forEach(listener => listener())
- }
- return {
- getState,
- dispatch,
- subscribe
- }
- }
- // 定义一个计数器的状态
- const countState = {
- count: 10
- }
- // 定一个方法叫changeState,用于处理state的数据,每次都返回一个新的状态
- const changeState = (state, action) => {
- switch(action.type) {
- // 处理减
- case 'COUNT_DECREMENT':
- state.count -= action.number
- break;
- // 处理加
- case 'COUNT_INCREMENT':
- state.count += action.number
- break;
- default:
- break;
- }
- }
-
- // 创建一个store
- const store = createStore(countState, changeState)
- // 定义一个方法用于渲染计数器的dom
- const renderCount = () => {
- const countDom = document.querySelector('#count')
- countDom.innerHTML = store.getState().count
- }
- // 初次渲染数据
- renderCount()
- // 监听,只要有dispatch,这个方法就会自动运行
- store.subscribe(renderCount)
- </script>
- </body>
- </html>
-
让changeState方法变为一个纯函数
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <meta http-equiv="X-UA-Compatible" content="ie=edge">
- <title>Redux principle 03</title>
- </head>
- <body>
- <h1>redux principle</h1>
- <div class="counter">
- <span class="btn" onclick="store.dispatch({type: 'COUNT_DECREMENT', number: 10})">-</span>
- <span class="count" id="count"></span>
- <span class="btn" id="add" onclick="store.dispatch({type: 'COUNT_INCREMENT', number: 10})">+</span>
- </div>
- <script>
- // 定义一个方法,用于集中管理state和dispatch
- const createStore = (state, changeState) => {
- // getState用于获取状态
- const getState = () => state
-
- // 定义一个监听器,用于管理一些方法
- const listeners = []
- const subscribe = (listener) => listeners.push(listener)
-
- // 定义一个dispatch方法,让每次有action传入的时候返回render执行之后的结果
- const dispatch = (action) => {
- // 调用changeState来处理数据
- state = changeState(state, action)
- // 让监听器里的所有方法运行
- listeners.forEach(listener => listener())
- }
- return {
- getState,
- dispatch,
- subscribe
- }
- }
- // 定义一个计数器的状态
- const countState = {
- count: 10
- }
- // 定一个方法叫changeState,用于处理state的数据,每次都返回一个新的状态
- const changeState = (state, action) => {
- switch(action.type) {
- // 处理减
- case 'COUNT_DECREMENT':
- return {
- ...state,
- count: state.count - action.number
- }
- // 处理加
- case 'COUNT_INCREMENT':
- return {
- ...state,
- count: state.count + action.number
- }
- default:
- return state
- }
- }
-
- // 创建一个store
- const store = createStore(countState, changeState)
- // 定义一个方法用于渲染计数器的dom
- const renderCount = () => {
- const countDom = document.querySelector('#count')
- countDom.innerHTML = store.getState().count
- }
- // 初次渲染数据
- renderCount()
- // 监听,只要有dispatch,这个方法就会自动运行
- store.subscribe(renderCount)
- </script>
- </body>
- </html>
-
合并state和changeState(最终版)
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <meta http-equiv="X-UA-Compatible" content="ie=edge">
- <title>Redux principle 04</title>
- </head>
- <body>
- <h1>redux principle</h1>
- <div class="counter">
- <span class="btn" onclick="store.dispatch({type: 'COUNT_DECREMENT', number: 10})">-</span>
- <span class="count" id="count"></span>
- <span class="btn" id="add" onclick="store.dispatch({type: 'COUNT_INCREMENT', number: 10})">+</span>
- </div>
- <script>
- // 定义一个方法,用于集中管理state和dispatch, changeState改名了,专业的叫法是reducer
- const createStore = (reducer) => {
- // 定义一个初始的state
- let state = null
- // getState用于获取状态
- const getState = () => state
-
- // 定义一个监听器,用于管理一些方法
- const listeners = []
- const subscribe = (listener) => listeners.push(listener)
-
- // 定义一个dispatch方法,让每次有action传入的时候返回reducer执行之后的结果
- const dispatch = (action) => {
- // 调用reducer来处理数据
- state = reducer(state, action)
- // 让监听器里的所有方法运行
- listeners.forEach(listener => listener())
- }
- // 初始化state
- dispatch({})
- return {
- getState,
- dispatch,
- subscribe
- }
- }
- // 定义一个计数器的状态
- const countState = {
- count: 10
- }
- // 定一个方法叫changeState,用于处理state的数据,每次都返回一个新的状态
- const changeState = (state, action) => {
- // 如果state是null, 就返回countState
- if (!state) return countState
- switch(action.type) {
- // 处理减
- case 'COUNT_DECREMENT':
- return {
- ...state,
- count: state.count - action.number
- }
- // 处理加
- case 'COUNT_INCREMENT':
- return {
- ...state,
- count: state.count + action.number
- }
- default:
- return state
- }
- }
-
- // 创建一个store
- const store = createStore(changeState)
- // 定义一个方法用于渲染计数器的dom
- const renderCount = () => {
- const countDom = document.querySelector('#count')
- countDom.innerHTML = store.getState().count
- }
- // 初次渲染数据
- renderCount()
- // 监听,只要有dispatch,renderCount就会自动运行
- store.subscribe(renderCount)
- </script>
- </body>
- </html>
-
Redux的流程:
Reducer必须是一个纯函数:
Reducer 函数最重要的特征是,它是一个纯函数。也就是说,只要是同样的输入,必定得到同样的输出。Reducer不是只有Redux里才有,之前学的数组方法reduce, 它的第一个参数就是一个reducer
纯函数是函数式编程的概念,必须遵守以下一些约束。
由于 Reducer 是纯函数,就可以保证同样的State,必定得到同样的 View。但也正因为这一点,Reducer 函数里面不能改变 State,必须返回一个全新的对象,请参考下面的写法。
- // State 是一个对象
- function reducer(state = defaultState, action) {
- return Object.assign({}, state, { thingToChange });
- // 或者
- return { ...state, ...newState };
- }
-
- // State 是一个数组
- function reducer(state = defaultState, action) {
- return [...state, newItem];
- }
-
最好把 State 对象设成只读。要得到新的 State,唯一办法就是生成一个新对象。这样的好处是,任何时候,与某个 View 对应的 State 总是一个不变(immutable)的对象。
我们可以通过在createStore中传入第二个参数来设置默认的state,但是这种形式只适合于只有一个reducer的时候。
- // src/store/index.js
- // cnpm i redux -S
- // 引入创建store的函数
- import { createStore } from 'redux'
-
- // const reducer = () => {}
- // const reducer = ( state, action) => {}
- // const reducer = ( state = { a: 1, b: 2}, action) => {}
- // const reducer = ( state = { a: 1, b: 2}, { type, payload }) => {}
-
- // 创建纯函数 - 设定初始化的值,修改状态
- // 纯函数 : 输入一定,输出一定确定
- // 纯函数中不写 Date.now() / Math.random()
- // 第一个参数代表 状态管理器中 的 初始化的状态 ,可以是常用的任何数据类型
- // 第二个参数 代表 用户操作的对应的行为以及传递的参数
- const reducer = (state = {
- username: '',
- list: []
- }, action) => {
- // type 代表用户的某个行为
- // payload 代表的传递的参数
- const { type, payload } = action
- // 改变状态
- switch (type) {
- case 'CHANGE_USERNAME':
- // 因为状态是只读的,需要处理,必须返回一个全新的对象
- // Object.assign()是 ES6 新增的 --- 拷贝对象
- return Object.assign({}, state, { username: payload })
- case 'CHANGE_LIST':
- // 扩展运算符合并对象 --- 拷贝对象
- return { ...state, list: payload }
- default:
- return state
- }
- }
- // 创建状态管理器
- const store = createStore(reducer)
-
- export default store
-
- // src/index.js
- import React from 'react'
- import ReactDOM from 'react-dom'
- import App from './App.jsx'
-
- import store from './07store/index'
- function render () {
- ReactDOM.render(
- <React.StrictMode>
- <App />
- </React.StrictMode>,
- document.querySelector('#root')
- )
- }
- render()
- console.log(store)
- // dispatch ----- 组件触发 action
- // getState ----- 获取组件的状态
- // subscribe ----- 订阅状态管理器的变化
- store.subscribe(render) // 如果状态管理器发生数据改变,执行render函数
-
- import React, { Component } from 'react'
- import store from './07store/index'
- export default class App extends Component {
-
- addFn () {
- const arr = store.getState().list
- arr.push(store.getState().username)
- store.dispatch({
- type: 'CHANGE_LIST',
- payload: arr
- })
- }
- deleteFn (index) {
- const arr = store.getState().list
- arr.splice(index, 1)
- store.dispatch({
- type: 'CHANGE_LIST',
- payload: arr
- })
- }
- render() {
- console.log(store.getState())
- const { username, list } = store.getState()
- return (
- <div>
- <input type="text" value={ username } onChange = {(e) => {
- // 修改状态
- store.dispatch({
- type: 'CHANGE_USERNAME',
- payload: e.target.value
- })
- }}/>
- <button onClick = { this.addFn.bind(this) }>添加</button>
- <ul>
- {
- list && list.map((item, index) => {
- return (
- <li key = { index }>
- { item }
- <button onClick = { () => {
- this.deleteFn(index)
- }}>X</button>
- </li>
- )
- })
- }
- </ul>
- </div>
- )
- }
- }
-
-
划分reducer:
因为一个应用中只能有一个大的state,这样的话reducer中的代码将会特别特别的多,那么就可以使用combineReducers方法将已经分开的reducer合并到一起
注意:
- 分离reducer的时候,每一个reducer维护的状态都应该不同
- 通过store.getState获取到的数据也是会按照reducers去划分的
- 划分多个reducer的时候,默认状态只能创建在reducer中,因为划分reducer的目的,就是为了让每一个reducer都去独立管理一部分状态
最开始一般基于计数器的例子讲解redux的基本使用即可。
关于action/reducer/store的更多概念,请查看官网
Redux异步
通常情况下,action只是一个对象,不能包含异步操作,这导致了很多创建action的逻辑只能写在组件中,代码量较多也不便于复用,同时对该部分代码测试的时候也比较困难,组件的业务逻辑也不清晰,使用中间件了之后,可以通过actionCreator异步编写action,这样代码就会拆分到actionCreator中,可维护性大大提高,可以方便于测试、复用,同时actionCreator还集成了异步操作中不同的action派发机制,减少编码过程中的代码量
常见的异步库:
基于Promise的异步库:
展示组件 | 容器组件 | |
---|---|---|
作用 | 描述如何展现(骨架、样式) | 描述如何运行(数据获取、状态更新) |
直接使用 Redux | 否 | 是 |
数据来源 | props | 监听 Redux state |
数据修改 | 从 props 调用回调函数 | 向 Redux 派发 actions |
调用方式 | 手动 | 通常由 React Redux 生成 |
可以先结合context来手动连接react和redux。
react-redux提供两个核心的api:
特别强调:
官网上的第二个参数为mapDispatchToProps, 实际上就是actionCreators
只要上层中有Provider组件并且提供了store, 那么,子孙级别的任何组件,要想使用store里的状态,都可以通过connect方法进行连接。如果只是想连接actionCreators,可以第一个参数传递为null
- // src/store/index.js
- import { createStore } from 'redux'
-
- const reducer = (state = {
- proList: [],
- skillList: []
- }, { type, payload }) => {
- switch (type) {
- case 'CHANGE_PRO_LIST':
- return { ...state, proList: payload }
- case 'CHANGE_SKILL_LIST':
- return { ...state, skillList: payload }
- default:
- return state
- }
- }
-
- const store = createStore(reducer)
-
- export default store
-
- import React from 'react'
- import ReactDOM from 'react-dom'
- import { Provider } from 'react-redux'
- import App from './App.jsx'
- import store from './store'
-
- ReactDOM.render(
- <React.StrictMode>
- <Provider store = { store }>
- <App />
- </Provider>
- </React.StrictMode>,
- document.querySelector('#root')
- )
-
-
- import React, { Component } from 'react'
- import { connect } from 'react-redux'
- class App extends Component {
- componentDidMount () {
- this.props.getProListData()
- this.props.getSkillListData()
- }
- render() {
- const { proList, skillList } = this.props
- return (
- <div>
- <h1>秒杀列表</h1>
- <ul>
- {
- skillList && skillList.map(item => {
- return (
- <li key = { item.proid }>
- { item.proname }
- </li>
- )
- })
- }
- </ul>
- <h1>产品列表</h1>
- <ul>
- {
- proList && proList.map(item => {
- return (
- <li key = { item.proid }>
- { item.proname }
- </li>
- )
- })
- }
- </ul>
- </div>
- )
- }
- }
- // const mapStateToProps = state => {
- // return {
- // proList: state.proList,
- // skillList: state.skillList
- // }
- // }
- const mapStateToProps = ({ proList, skillList }) => ({ proList, skillList })
-
- const mapDispatchToProps = (dispatch) => {
- return {
- getProListData () {
- fetch('http://121.89.205.189/api/pro/list')
- .then(res => res.json())
- .then(res => {
- dispatch({
- type: 'CHANGE_PRO_LIST',
- payload: res.data
- })
- })
- },
- getSkillListData () {
- fetch('http://121.89.205.189/api/pro/seckilllist')
- .then(res => res.json())
- .then(res => {
- dispatch({
- type: 'CHANGE_SKILL_LIST',
- payload: res.data
- })
- })
- }
- }
- }
- export default connect(mapStateToProps, mapDispatchToProps)(App)
-
-
分reducer / 异步处理action