Redux
Redux是JavaScript應⽤的狀態容器。它保證程序⾏爲⼀致性且易於測試。
安裝
npm i redux -S
過程:
- 需要一個store來存儲數據
- store裏的
reducer
初始化state並定義state修改規則 - 通過dispatch一個action來提交對數據的修改
- action提交到reducer函數裏,根據傳入的action的type,返回的新的state
redux概念:
- createStore: 創建store
- reducer: 初始化、修改狀態函數
- getState:獲取狀態值
- dispatch 提交更新
- subscribe 變更訂閱
例子:
創建store:
// src/store/store.js
import {createStore} from 'redux'
const counterReducer = (state = 0,action) =>{
switch(action.type){
case "add":
return state+1;
case 'minus':
return state-1;
default:
return state;
}
}
const store = createStore(counterReducer);
export default store;
// reduxpage.js
import React,{Component} from 'react'
import store from '../store/reduxStore'
export default class ReduxPage extends Component{
componentDidMount(){
store.subscribe(()=>{ // 訂閱更新,當數據發生變化時
this.forceUpdate(); // 強制更新
})
}
add= ()=>{
store.dispatch({type:'add'})
}
minus=()=>{
store.dispatch({type:'minus'});
}
render(){
return(
<div>
<h1>reduxPage</h1>
<p>{store.getState()}</p>
<button onClick={this.add}>add</button>
<button onClick={this.minus}>minus</button>
</div>
)
}
}
react-redux
使用redux的話,需要重新調用render,獲取數據要通過getState,爲此引入react-redux。
安裝
npm install react-redux --save
react-redux提供了兩個api
- Provider 爲其他組件提供store
- connect 爲組件提供數據的變更方法
創建兩個reducer:
// counterReducer.js
const counterReducer = (state = 0,action) =>{
switch(action.type){
case "add":
return state+1;
case 'minus':
return state-1;
default:
return state;
}
}
export default counterReducer;
// multipleReducer.js
const multipleReducer = (state = 1,action) =>{
switch(action.type){
case "double":
return state*2;
default:
return state;
}
}
export default multipleReducer;
// reactRedux.js
import {createStore,combineReducers} from 'redux'
import counterReducer from './counterReducer'
import multipleReducer from './multipleReducer'
const store = createStore(combineReducers({ // combineReducers 結合多個reducers
counter:counterReducer,
multi:multipleReducer
}))
export default store;
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import Page from './pages3/page'
import store from './store/reactRedux'
import {Provider} from 'react-redux'
const jsx = (
<div>
<Provider store={store}>
<Page/>
</Provider>
</div>
)
ReactDOM.render(
jsx,
document.getElementById('root')
);
獲取狀態數據
// page.js
import React,{Component} from 'react'
import {connect} from 'react-redux'
class ReactReduxPage extends Component{
render(){
console.log(this.props) // props中可以獲取到connect方法中所有映射過來的值跟方法
const {num,add,minus} = this.props;
return (
<div>
<p>{num}</p>
<button onClick={add}>add</button>
<button onClick={minus}>minus</button>
</div>
)
}
}
const mapStateToProps = state =>{ // 映射state到props中
return {
num:state.multi,
numadd:state.counter
}
}
const mapDispatchToProps={ // 映射dispatch到props中
add:()=>{
return {
type:'add'
}
},
minus:()=>{
return {
type:'minus'
}
}
}
export default connect( // 獲取provider中傳過來的值
mapStateToProps,
mapDispatchToProps
)(ReactReduxPage)
異步
Redux只是個純粹的狀態管理器,默認只⽀持同步,實現異步任務⽐如延遲,⽹絡請求,需要中間件的⽀持,安裝redux-thunk
npm i redux-thunk -S
在沒有使用中間件的時候調用異步會報錯。
asyncAdd:()=>{
setTimeout(()=>{
return {
type:'double'
}
})
}
使用中間件
// reactRedux.js
import {createStore,combineReducers,applyMiddleware} from 'redux'
import counterReducer from './counterReducer'
import multipleReducer from './multipleReducer'
import thunk from 'redux-thunk'
const store = createStore(combineReducers({
counter:counterReducer,
multi:multipleReducer
}),applyMiddleware(thunk))
export default store;
// page.js
asyncAdd:()=>dispatch=>{
setTimeout(()=>{
dispatch({type:'double'})
},1000)
}
Redux原理
核心:
- state 狀態
- getState 獲取狀態
- dispatch 更新
- subscribe 訂閱變更
實現原理
export function createStore(reducer,enhancer){
let currentState = undefined; //當前狀態
let currentListeners = [];
function getState(){ // 獲取當前狀態
return currentState;
}
function subscribe(listener){ // 訂閱變更
currentListeners.push(listener)
}
function dispatch(action){ // 變更狀態
currentState = reducer(currentState,action)
currentListeners.forEach(v=>v());
return action;
}
return {getState,subscribe,dispatch}
}
// store.js
import {createStore} from '../lRedux'
const counterReducer = (state = 0,action) =>{
switch(action.type){
case "add":
return state+1;
case 'minus':
return state-1;
default:
return state;
}
}
const store = createStore(counterReducer);
export default store;
// page.js
import React,{Component} from 'react'
import store from '../store/store'
export default class LReduxPage extends Component{
componentDidMount(){
store.subscribe(()=>{
this.forceUpdate();
})
}
add= ()=>{
console.log(store.dispatch({type:'add'}))
}
minus=()=>{
store.dispatch({type:'minus'});
}
render(){
console.log('store',store);
return(
<div>
<h1>reduxPage</h1>
<p>{store.getState()}</p>
<button onClick={this.add}>add</button>
<button onClick={this.minus}>minus</button>
</div>
)
}
}
中間件的實現
重點就是 實現函數序列執行
export function createStore(reducer,enhancer){
if(enhancer){
return enhancer(createStore)(reducer) // 增強store,將createStore傳進去
}
let currentState = undefined;
let currentListeners = [];
function getState(){
return currentState;
}
function subscribe(listener){
currentListeners.push(listener)
}
function dispatch(action){
currentState = reducer(currentState,action)
currentListeners.forEach(v=>v());
return action;
}
dispatch({type:'default'})
return {getState,subscribe,dispatch}
}
export function applyMiddleware(...middlewares){ // 使用中間件
return createStore=>(...args)=>{ // 接受createStore作爲參數
const store = createStore(...args); // 獲取到reducer對應的store
let dispatch = store.dispatch; // 獲取dispatch方法
const midApi = { // 中間件的api
getState:store.getState, // 獲取state
dispatch:(...args)=>dispatch(...args) // 獲取reducer對應的store中的dispatch方法
}
const middlewareChain = middlewares.map(middleware=>middleware(midApi)) // 傳遞給所有的中間件
dispatch = compose(...middlewareChain) // 組合所有的middleware
(store.dispatch) // 將dispatch傳進組合後的方法中
console.log({...store,dispatch})
return {...store,dispatch} // 返回整合後的store
}
}
export function compose(...funcs){ // 組合
if(funcs.length === 0){
return arg => arg
// return ()=>{} // 返回一個函數
}
if(funcs.length === 1){
return funcs[0]
}
return funcs.reduce((a,b)=>(...args)=>a(b(...args)))
}
// store.js
import {createStore,applyMiddleware} from '../lRedux'
const counterReducer = (state = 0,action) =>{
switch(action.type){
case "add":
return state+1;
case 'minus':
return state-1;
default:
return state;
}
}
const store = createStore(counterReducer,applyMiddleware(logger)); // 使用了中間件
export default store;
function logger(){
return dispatch => action =>{
action.type && console.log(`${action.type} 已經執行了`);
return dispatch(action)
}
}
redux-thunk 原理
thunk增加了處理函數型action的能⼒。
// store.js
import {createStore,applyMiddleware} from '../lRedux'
const counterReducer = (state = 0,action) =>{
switch(action.type){
case "add":
return state+1;
case 'minus':
return state-1;
default:
return state;
}
}
const store = createStore(counterReducer,applyMiddleware(thunk,logger));
console.log(store)
export default store;
function logger(){
return dispatch => action =>{
action.type && console.log(`${action.type} 已經執行了`);
return dispatch(action)
}
}
function thunk({getState}){
return dispatch=>action=>{
if(typeof action === 'function'){ // 傳入函數
return action(dispatch,getState)
}else{
return dispatch(action)
}
}
}
// page.js
asyncAdd=()=>{
store.dispatch(dispatch=>{ // dispatch中傳入函數
setTimeout(()=>{
dispatch({type:'add'})
},1000)
})
}
react-redux原理
核心:
- 高階函數connect,可以根據傳⼊狀態映射規則函數和派發器映射規則函數映射需要的屬性,可以處理變更檢測和刷新任務。
- Provider組件傳遞store
實現react-redux:
// lreact-redux.js
import React from 'react'
import PropTypes from 'prop-types'
import {bindActionCreators} from './lRedux'
// connect()() // 傳入兩個參數,將state、dispatch映射到props中,返回一個傳入組件的函數
export const connect = (
mapStateToProps = state=>state,
mapDispatchToProps = {}
) => (WrapComponent) => {
return class ConnectComponent extends React.Component{
static contextTypes={ // class組件聲明靜態contextTypes可以獲取上下文Context
store:PropTypes.object
}
constructor(props,context){ // 傳入props,context
super(props,context);
this.state={
props:{}
}
}
componentDidMount(){
const {store} = this.context // 從上下文中獲取store
store.subscribe(()=>this.update()) // 訂閱更新
this.update() // 執行更新
}
update(){
const {store} = this.context;
const stateProps = mapStateToProps(store.getState()); // 獲取state
const dispatchProps = bindActionCreators(mapDispatchToProps,store.dispatch) // 獲取dispatch
console.log(dispatchProps)
this.setState({
props:{ // 往props傳入所有的值
...this.state.props,
...stateProps,
...dispatchProps
}
})
}
render(){ // 重新渲染傳入的組件,並傳入所有的props
return <WrapComponent {...this.state.props} />
}
}
}
export class Provider extends React.Component{ // 提供store
static childContextTypes={
store:PropTypes.object
}
getChildContext(){
return {store:this.store}
}
constructor(props,context){
super(props,context)
this.store = props.store // 傳入組件的store <Provider store={store} />
}
render(){
return this.props.children
}
}
lredux中加入解析action的方法
function bindActionCreator(creator,dispatch){
return (...args) => dispatch(creator(...args))
}
// creators 的格式
// add:()=> {return {type:'add'}}
/**
* asyncAdd:()=>dispatch=>{
setTimeout(()=>{
dispatch({type:'double'})
},1000)
}
*/
export function bindActionCreators(creators,dispatch){
return Object.keys(creators).reduce((ret,item)=>{
ret[item]= bindActionCreator(creators[item],dispatch)
console.log(ret[item])
return ret
},{})
}
最後在index.js中引入Provider
import {Provider} from './nativeRedux/lreact-redux'
...
<Provider store={store}>
<Page />
</Provider>