這一次總結的是 React-redux 的實現,能夠參考一下 大佬的文章 。html
首先要知道
redux
的基本使用:react
建立一個 Store
。git
<!-- store -->
function createStore (reducer) {
let state = null
const listeners = []
const subscribe = (listener) => listeners.push(listener)
const getState = () => state
const dispatch = (action) => {
state = reducer(state, action)
listeners.forEach((listener) => listener())
}
dispatch({}) // 初始化 state
return { getState, dispatch, subscribe }
}
<!-- reducer -->
const themeReducer = (state = {}, action) => {
switch (action.type) {
case 'CHANGE_COLOR':
return { ...state, themeColor: action.themeColor }
default:
return state
}
}
<!-- 建立 store -->
const store = createStore(themeReducer)
複製代碼
Store
是保存數據的地方,整個應用只有一個,調用CreateStore
函數而且傳入一個Reducer
來建立一個Store
,而且會返回新的Store
對象。github
獲取當前的 State
。redux
<!-- 調用 store.getState 獲取當前的狀態 -->
const state = store.getState()
複製代碼
State
是Store
裏面包含的數據對象,能夠經過Store.getState()
獲取數組
經過 Dispatch
發送 Action
改變 State
。bash
<!-- 調用 dispatch 發送 action -->
store.dispatch({
type: 'CHANGE_COLOR',
themeColor: 'blue'
})
複製代碼
Action
就是View
發出的通知,表示View
要變化,其中Type
是必須的,其他能夠 自定義 。app
若是要寫多個
Action
以爲麻煩,可使用Action Creator
函數來生產Action
。dom
function updateThemeColor (action) {
type: action.type,
themeColor: action.themeColor
}
store.dispatch( updateThemeColor({ type: 'CHANGE_COLOR', themeColor: 'blue' }) )
複製代碼
Reducer
是 Store
收到 Action
以後用來計算 State
而且返回新的 State
,也就是說必需要有 Return
。異步
<!-- reducer -->
<!-- 初始 state 是必須的,redux 規定不能爲 undefined 和 null -->
const themeReducer = (state = {}, action) => {
switch (action.type) {
case 'CHANGE_COLOR':
return { ...state, themeColor: action.themeColor }
default:
return state
}
}
複製代碼
Reducer
能夠根據不一樣的Type
來進行不一樣的邏輯處理,而且每次都會返回新的state
來覆蓋原來的state
。
Reducer
是一個純函數,一樣的輸入就會獲得一樣的輸出。
Reducer
必需要返回一個新的狀態,而不是改變原有的狀態,請參考下面寫法:
// State 是一個對象
function reducer(state, action) {
return Object.assign({}, state, { thingToChange });
// 或者
return { ...state, ...newState };
}
// State 是一個數組
function reducer(state, action) {
return [...state, newItem];
}
複製代碼
調用 subscribe
傳入一個函數,狀態改變時會調用此函數。
store.subscribe(()=> {
ReactDOM.render()
})
複製代碼
Store.subscribe
方法設置監聽函數,一旦State
發生變化,就自動執行這個函數。
通常傳入
render
和this.setState()
來監聽頁面從新渲染。
調用此方法會返回一個函數,調用函數以後能夠解除監聽。
首先以前向組件傳遞參數時,第一次使用的是 狀態提高,即經過父級傳入一個函數而後拿到組件裏面的東西,再傳入另外一個組件裏面。
當嵌套的太多層時,使用 狀態提高 會很是麻煩,而後第二次就開始使用了 Context ,因爲 Context 能隨意被改變,這時咱們能夠把 Context 和 Store 結合使用,這樣就不能隨意的改變 Context ,而且狀態還能共享。
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
// 引入組件
import { Header } from './component/Header';
import { Content } from './component/Content';
import * as serviceWorker from './serviceWorker';
<!-- store -->
function createStore(reducer) {
let state = null;
const listeners = [];
const subscribe = (listener) => listeners.push(listener);
const getState = () => state;
const dispatch = (action) => {
state = reducer(state, action);
listeners.forEach((listener) => listener());
}
dispatch({});
return { getState, dispatch, subscribe };
}
<!-- reducer -->
const themeReducer = (state, action) => {
if (!state) return {
themeColor: 'red'
}
switch (action.type) {
case 'CHANGE_COLOR':
return { ...state, themeColor: action.themeColor }
default:
return state
}
}
<!-- 建立 store -->
const store = createStore(themeReducer);
class Index extends Component {
<!-- 設置子組件的 contextType -->
static childContextTypes = {
store: PropTypes.object
}
<!-- 設置 context -->
getChildContext() {
return { store }
}
render() {
return (
<div className="index">
<Header />
<Content />
</div>
)
}
}
ReactDOM.render(<Index />, document.getElementById('root'));
複製代碼
建立 store 而後把它放到 context 裏面,這樣全部子組件均可以拿到了。
<!-- Header -->
import React, { Component } from 'react';
import PropTypes from 'prop-types';
class Header extends Component {
static contextTypes = {
store: PropTypes.object
}
constructor(props) {
super(props);
this.state = {
themeColor: ''
}
}
componentWillMount() {
let { store } = this.context;
this._updateThemeColor();
store.subscribe(() => this._updateThemeColor())
}
_updateThemeColor() {
let state = this.context.store.getState();
this.setState({
themeColor: state.themeColor
})
}
render() {
return (
<div className="header">
<h1 style={{color: this.state.themeColor}}>is header</h1>
</div>
)
}
}
export { Header };
<!-- Content-->
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { ThemeSwitch } from './ThemeSwitch';
class Content extends Component {
static contextTypes = {
store: PropTypes.object
}
componentWillMount() {
let { store } = this.context;
this._updateThemeColor();
store.subscribe(() => this._updateThemeColor())
}
_updateThemeColor() {
let state = this.context.store.getState();
this.setState({
themeColor: state.themeColor
})
}
render() {
return (
<div className="header">
<h2 style={{ color: this.state.themeColor }}>is Content</h2>
<ThemeSwitch />
</div>
)
}
}
export { Content };
<!-- ThemeSwitch -->
import React, { Component } from 'react';
import PropTypes from 'prop-types';
class ThemeSwitch extends Component {
static contextTypes = {
store: PropTypes.object
}
componentWillMount() {
let { store } = this.context;
this._updateThemeColor();
store.subscribe(() => this._updateThemeColor())
}
_updateThemeColor() {
let state = this.context.store.getState();
this.setState({
themeColor: state.themeColor
})
}
render() {
return (
<div className="header">
<button style={{ color: this.state.themeColor }}>red</button>
<button style={{ color: this.state.themeColor }}>blue</button>
</div>
)
}
}
export { ThemeSwitch };
複製代碼
上面三個子組件使用 store.getState() 獲取到 reducer 設置的默認狀態,這樣的話就能夠實現共享狀態了。
接下來咱們實現點擊按鈕改變顏色:
<!-- ThemeSwitch -->
updateThemeColor(color) {
let { store } = this.context;
store.dispatch({
type: 'CHANGE_COLOR',
themeColor: color
})
}
render() {
return (
<div className="header">
<button style={{ color: this.state.themeColor }} onClick={this.updateThemeColor.bind(this, 'red')}>red</button>
<button style={{ color: this.state.themeColor }} onClick={this.updateThemeColor.bind(this, 'blue')}>blue</button>
</div>
)
}
複製代碼
調用 dispatch 而後傳入 action ,而後會調用 reducer 函數,而後根據傳入的 action.type 改變狀態,以後再返回一個新的狀態。
返回新狀態時要想監聽頁面的更新,能夠在 subscribe 傳入要監聽的函數,這樣就能夠在調用 dispatch 同時會調用你傳入的函數,而後再一次調用 this.setState 觸發頁面從新渲染。
_updateThemeColor() {
<!-- 從新獲取一次狀態 -->
let state = this.context.store.getState();
<!-- 從新設置,而且觸發從新渲染 -->
this.setState({
themeColor: state.themeColor
})
}
componentWillMount() {
let { store } = this.context;
<!-- 首次渲染 -->
this._updateThemeColor();
store.subscribe(() => this._updateThemeColor())
}
複製代碼
上面的組件有着重複的邏輯,首先取出 store 的 state 而後設置成本身的狀態,還有一個就是對 context 依賴過強,這時咱們能夠利用 高階組件 來和 context 打交道,這時就不用每一個組件都獲取一遍 store 了。
import React, { Component } from 'react';
import PropTypes from 'prop-types';
<!-- 接受一個組件 -->
const connect = (WrappedComponent) => {
class Connect extends Component {
static contextTypes = {
store: PropTypes.object
}
render() {
const { store } = this.context;
return <WrappedComponent />
}
}
return Connect;
}
export { connect };
複製代碼
connect 是用於從 UI 組件生成 容器組件 ,也就是說咱們傳入的組件只是負責呈現和展現,而 容器組件 負責業務邏輯和帶有內部狀態,connect 負責的是將二者合併起來,生成並返回新的組件。
因爲每一個傳進去的組件須要的 store 裏面的數據都不同,因此咱們還要傳入一個函數來告訴 高階組件 正確獲取數據。
const mapStateToProps = (state) {
return {
themeColor: state.themeColor
}
}
複製代碼
mapStateToProps 是一個獲取 store 保存的狀態,而後將這個狀態轉化爲 UI組件 的數據的函數,它必需要返回一個對象,而這個對象用來進行狀態轉化的。
import React, { Component } from 'react';
import PropTypes from 'prop-types';
const connect = (mapStateToProps) => (WrappedComponent) => {
class Connect extends Component {
static contextTypes = {
store: PropTypes.object
}
render () {
const { store } = this.context;
<!-- 從store獲取state而且轉化以後所有傳入 props -->
let stateProps = mapStateToProps(store.getState());
return <WrappedComponent {...stateProps} />
}
}
return Connect;
}
export { connect };
複製代碼
上面最關鍵的一步就是調用 mapStateToProps 時,從 store 獲取到 state 以後而後傳入到 mapStateToProps 函數中,而後這函數會返回一個轉化後的 state ,而後把這些轉化的狀態所有傳入 props 裏面。
能夠看出 connect 把 Dumb組件(純組件) 和 context 連起來了,下面只須要調用 connect 而後傳入一個 mapStateToProps 和 UI組件 就可使用了。
<!-- Header -->
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from './React-redux';
class Header extends Component {
static propTypes = {
themeColor: PropTypes.string
}
render() {
return (
<div className="header">
<h1 style={{color: this.props.themeColor}}>is header</h1>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
themeColor: state.themeColor
}
}
Header = connect(mapStateToProps)(Header)
export { Header };
<!-- Content -->
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from './React-redux';
import { ThemeSwitch } from './ThemeSwitch';
class Content extends Component {
static propTypes = {
themeColor: PropTypes.string
}
render() {
return (
<div className="header">
<h2 style={{ color: this.props.themeColor }}>is Content</h2>
<ThemeSwitch />
</div>
)
}
}
const mapStateToProps = (state) => {
return {
themeColor: state.themeColor
}
}
Content = connect(mapStateToProps)(Content)
export { Content };
複製代碼
因爲 mapStateToProps 返回的對象通過 connect 傳入組件的 props 中,咱們直接能夠用 this.props 直接獲取到。
接着把 connect 的代碼複製到一個叫 React-redux 的文件,而後能夠刪掉以前那些引入 store 的代碼了。
如今點擊按鈕只有按鈕會變顏色,接下來咱們修改一下 connect :
import React, { Component } from 'react';
import PropTypes from 'prop-types';
const connect = (mapStateToProps) => (WrappedComponent) => {
class Connect extends Component {
static contextTypes = {
store: PropTypes.object
}
constructor (props) {
super(props);
this.state = {
allProps: {}
}
}
componentWillMount () {
const { store } = this.context;
this._updateProps();
store.subscribe(() => this._updateProps())
}
_updateProps () {
const { store } = this.context
<!-- 如今 mapStateToProps 能夠接受兩個參數 -->
let stateProps = mapStateToProps(store.getState(), this.props)
<!-- 整合普通的 props 和從 state 生成的 props -->
this.setState({
allProps: {
...stateProps,
...this.props
}
})
}
render () {
return <WrappedComponent { ...this.state.allProps } />
}
}
return Connect;
}
export { connect };
複製代碼
每次點擊按鈕調用 dispatch 都會把心的 state 設置到本身的 state 以後,而後返回給組件,這樣組件以前的 props 也會保留,同時 mapStateToProps 能夠接受第二個參數,這個參數爲當前 UI組件 的 props 。
<!-- 第一個爲 store 獲取到的 state , 第二個爲當前 ui 組件的 props (不是最新的) -->
const mapStateToProps = (state, props) => {
console.log(state, props)
return {
themeColor: state.themeColor
}
}
複製代碼
使用 props 做爲參數後,若是容器組件的參數發生變化,也會引起 UI組件 從新渲染,connect 方法能夠省略 mapStateToProps 參數,這樣 store 的更新不會引發組件的更新。
const mapDispatchToProps = (dispatch, props) => {
return {
updateThemeColor: () => {
dispatch({
type: 'CHANGE_COLOR',
payload: ''
})
}
}
}
複製代碼
mapDispatchToProps 是 connect 的第二個參數,用來創建 UI組件 的參數到 store.dispatch 方法的映射,它做爲函數時能夠接受兩個參數,一個是 dispatch ,一個則是 UI組件 的 props 。
mapDispatchToProps 能夠定義 action 而後傳給 store 。
import React, { Component } from 'react';
import PropTypes from 'prop-types';
const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => {
class Connect extends Component {
static contextTypes = {
store: PropTypes.object
}
constructor (props) {
super(props);
this.state = {
allProps: {}
}
}
componentWillMount () {
const { store } = this.context;
this._updateProps();
store.subscribe(() => this._updateProps())
}
_updateProps () {
const { store } = this.context
let stateProps = mapStateToProps
? mapStateToProps(store.getState(), this.props)
: {}
let dispatchProps = mapDispatchToProps
? mapDispatchToProps(store.dispatch, this.props)
: {}
this.setState({
allProps: {
...stateProps,
...dispatchProps,
...this.props
}
})
}
render () {
return <WrappedComponent { ...this.state.allProps } />
}
}
return Connect;
}
export { connect };
複製代碼
接受 mapDispatchToProps 做第二個參數,調用時把 dispatch 和 props 傳進去,返回 onClickUpdate 而後直接傳入 props 中返回給 UI組件 ,接着咱們能夠直接調用 this.props.onClickUpdate 而後調用 dispatch 來更新狀態。
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from './React-redux';
class ThemeSwitch extends Component {
static contextTypes = {
onClickUpdate: PropTypes.func
}
<!-- 點擊調用 onClickUpdate -->
updateThemeColor(color) {
if(this.props.onClickUpdate) {
this.props.onClickUpdate(color)
}
}
render() {
return (
<div className="header">
<button style={{ color: this.props.themeColor }} onClick={this.updateThemeColor.bind(this, 'red')}>red</button>
<button style={{ color: this.props.themeColor }} onClick={this.updateThemeColor.bind(this, 'blue')}>blue</button>
</div>
)
}
}
<!-- 在真正的 react-redux 不必定是函數 -->
const mapStateToProps = (state, props) => {
return {
themeColor: state.themeColor
}
}
<!-- 在真正的 react-redux 能夠是一個對象 -->
const mapDispatchToProps = (dispatch, props) => {
return {
onClickUpdate: (color) => {
dispatch({
type: 'CHANGE_COLOR',
themeColor: color
})
}
}
}
ThemeSwitch = connect(mapStateToProps, mapDispatchToProps)(ThemeSwitch);
export { ThemeSwitch };
複製代碼
這樣點擊按鈕以後又能夠改變顏色了。
connect 方法生成容器後須要拿到 state 對象,目前我們能拿到 store 是由於在
index.js
中設置了 context ,這樣會直接污染index.js
, React-redux 提供了 Provider 來充當最外層容器,這樣就不須要在index
設置 context 了。
class Provider extends Component {
static propTypes = {
store: PropTypes.object,
children: PropTypes.any
}
static childContextTypes = {
store: PropTypes.object
}
getChildContext () {
return {
store: this.props.store
}
}
render () {
return (
<div>{this.props.children}</div>
)
}
}
export { Provider };
複製代碼
在 React-redux 的文件增長上面的代碼,其實也就是另外設置一個容器來替代以前
index.js
乾的活,這裏返回了 this.props.children ,也說明要用這個組件把其餘的組件包起來。
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import { Provider } from './component/React-redux';
// 引入組件
import { Header } from './component/Header';
import { Content } from './component/Content';
import * as serviceWorker from './serviceWorker';
function createStore(reducer) {
let state = null;
const listeners = [];
const subscribe = (listener) => listeners.push(listener);
const getState = () => state;
const dispatch = (action) => {
state = reducer(state, action);
listeners.forEach((listener) => listener());
}
dispatch({});
return { getState, dispatch, subscribe };
}
const themeReducer = (state, action) => {
if (!state) return {
themeColor: 'red'
}
switch (action.type) {
case 'CHANGE_COLOR':
return { ...state, themeColor: action.themeColor }
default:
return state
}
}
const store = createStore(themeReducer);
class Index extends Component {
render() {
return (
<div className="index">
<Header />
<Content />
</div>
)
}
}
ReactDOM.render(
<!-- 把 store 和 外層組件包起來 -->
<Provider store= { store }>
<Index />
</Provider>,
document.getElementById('root')
);
複製代碼
把 Store 傳入給 Provider ,而後它把 store 設置成 context ,這樣其餘子組件都能拿到 store ,而且把最外層容器包起來,而後使用 this.props.children 所有羅列出來。
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from './React-redux';
class Header extends Component {
<!-- 別忘了聲明這玩意,否則拿不到 -->
static contextTypes = {
store: PropTypes.object
}
static propTypes = {
themeColor: PropTypes.string
}
componentWillMount() {
console.log(this.context)
}
render() {
return (
<div className="header">
<h1 style={{color: this.props.themeColor}}>is header</h1>
</div>
)
}
}
const mapStateToProps = (state, props) => {
return {
themeColor: state.themeColor
}
}
Header = connect(mapStateToProps)(Header)
export { Header };
複製代碼
拿到以後接下來就能夠浪了,能夠在當前組件調用裏面的方法,很是靈活。
index.js
引入建立好的 store
,而後引入 Provider
把 index
包起來,而且給它傳遞 store
。store.getState
,若是想監聽函數調用 store.subscribe
傳入函數。store
或者修改 state
,在當前組件引入 connect
接着傳入 mapStateToProps
和 mapDispatchToProps
來呈現新的 UI組件
。dispatch
還能夠拓展,真正的 react-redux
還可使用中間件實現異步 action
,如須要從後臺返回的狀態來改變當前的 state
相似這種操做。combineReducers
管理多個 reducer
,一個 store
管理N種狀態。