適用場景
UI層簡單,沒有什么互動的,不需要redux
使用方式復雜哼蛆,與服務(wù)器大量交互构拳,視圖層要從多個來源獲取數(shù)據(jù)的咆爽,使用redux
比如說:
- 組件狀態(tài)要功響
- 一個組件更改全局狀態(tài)
- 一個組件需要改變另一個組件的狀態(tài)
設(shè)計思想
web應(yīng)用是個狀態(tài)機,視圖與狀態(tài)一一對應(yīng)置森。
所有的狀態(tài)斗埂,保存在一個對象里。
基本使用
npm i -g create-react-app
安裝包
create-react-app redux-api
創(chuàng)建項目
npm i redux
安裝redux
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux'
// 1.定義reducer
// state:容器的初始狀態(tài)
// action:修改state的行為類型
// type - 行為類型凫海; payload(可選的)其他數(shù)據(jù)
function Reducer(state = 0, action) {
const {type} = action
if (type === 'INCREMENT') {
return state + 1
} else if (type === 'DECREMENT') {
return state - 1
} else {
return state
}
}
// 2.創(chuàng)建store
const store = createStore(Reducer, 110)
// 3.獲取狀態(tài)
console.log(store.getState())
// 4.更新狀態(tài)
// store.dispatch({type:行 為類型呛凶,額外參數(shù)})
// 發(fā)起dispatch就是在調(diào)用reducer,dispatch接收的參數(shù)叫action
setTimeout(() => {
store.dispatch({
type: 'INCREMENT'
})
}, 1000);
// 5.檢測狀態(tài)變化行贪,驅(qū)動視圖更新
store.subscribe(() => {
console.log(store.getState())
render()
})
const Counter = (props) => {
return <div>
<h1>{props.value}</h1>
<button>Increment</button>
<button>Decrement</button>
</div>
}
function render() {
ReactDOM.render(<Counter value={store.getState()}></Counter>, document.getElementById('root'));
}
render()
核心概念
-
store
保存數(shù)據(jù)的容器漾稀,通過createStore
來生成模闲。
import { createStore } from 'redux'
const store = createStore(Reducer, 110)
-
state
包含所有的數(shù)據(jù)。
一個state
對應(yīng)一個view
崭捍。
const state = store.getState()
-
action
view
發(fā)出action
尸折,通知state
進行改變。
action
是一個對象殷蛇,type
屬性必須有实夹,表示名稱。其他屬性自由設(shè)置粒梦。
action
是改變state
的唯一方法亮航。
const action = {
type: 'INCREMENT',
payload: 'learn redux'
}
-
action creator
生成action
的函數(shù)
const ADD_TODO = '添加todo'
function addTodo(text) {
return {
type: ADD_TODO,
text
}
}
const action = addTodo('learn redux')
-
store.dispatch
view
通過此方法發(fā)出action
。也是唯一的方法匀们。
store.dispatch
接受action
對象作為參數(shù)塞赂,然后將其發(fā)送出去。
store.dispatch({
type: 'INCREMENT',
payload: 'learn redux'
})
store.dispatch(addTodo('learn redux'))
-
reducer
state
的計算過程昼蛀。接受action
和當前state
作為參數(shù)宴猾,返回新的state
。
function Reducer(state = 0, action) {
const {type} = action
if (type === 'INCREMENT') {
return state + 1
} else if (type === 'DECREMENT') {
return state - 1
} else {
return state
}
}
一般情況下叼旋,store.dispatch
方法會觸發(fā)reducer
的自動執(zhí)行仇哆,所以store
生成時,需要傳入reducer
const store = createStore(Reducer, 110)
reducer
是純函數(shù)夫植,同樣的輸入讹剔,必定得到同樣的輸出。
reducer
函數(shù)里的state
不能改變详民,必須返回一個全新的對象延欠。最好把state
設(shè)置為只讀。
// state是對象
function reducer(state, action) {
return { ...state, ...newState}
}
// state是數(shù)組
function reducer(state, action) {
return [ ...state, newItem]
}
-
store.subscribe()
監(jiān)聽方法沈跨,state
改變由捎,就會立即執(zhí)行。
把view
更新的函數(shù)放入其中饿凛,就會實現(xiàn)view
的自動渲染狞玛。
store.subscribe(() => {
console.log(store.getState())
render()
})
如果store.subscribe
方法返回函數(shù),調(diào)用這個函數(shù)涧窒,即可解除監(jiān)聽
let unsubscribe = store.subscribe(() => {
connsole.log(store.getState())
})
unsubscribe() // 解除監(jiān)聽
reducer的拆分
項目中state
一般很龐大心肪,所以需要對reducer
進行拆分
拆分前的代碼:
const Reducer = (state = {}, action = {}) => {
const { type, payload } = action
switch (type) {
case 'ADD_CHAT':
return Object.assign({}, state, {
charLog: state.charLog.concat(payload)
})
case 'CHANGE_STATUS':
return Object.assign({}, state, {
statusMessage: payload
})
case 'CHANGE_USERNAME':
return Object.assign({}, state, {
userName: payload
})
default: return state
}
}
拆分后的文件結(jié)構(gòu)
chatLog.js
export default function chatLog (state = [], action) {
const { type, payload } = action
switch (type) {
case 'ADD_CHAT':
return [...state, payload]
default: return state
}
}
statusMessage.js
export default function statusMessage (state = '', action) {
const { type, payload } = action
switch (type) {
case 'CHANGE_STATUS':
return payload
default: return state
}
}
userName.js
export default function userName (state = '', action) {
const { type, payload } = action
switch (type) {
case 'CHANGE_USERNAME':
return payload
default: return state
}
}
index.js
import chatLog from './chatLog'
import statusMessage from './statusMessage'
import userNameChange from './userName'
import { combineReducers } from 'redux'
// 寫法1:不推薦
// export default function (state = {}, action = {}) {
// return {
// chatLog: chatLog(state.chatLog, action),
// statusMessage: statusMessage(state.statusMessage, action),
// userName: userNameChange(state.userName, action)
// }
// }
// 寫法2:推薦
export default combineReducers({
chatLog,
statusMessage,
userName: userNameChange
})
當需要使用拆分后的reducer
時,只需要在src
的index.js
下
import rootReducer from './reducer'
const store = createStore(rootReducer)
中間件
位于“中間”纠吴,為“兩側(cè)”提供服務(wù)的函數(shù)硬鞍。
redux-logger
npm i redux-logger
安裝包
index.js
中修改下面幾處:
import { applyMiddleware, createStore } from 'redux'
import { createLogger } from 'redux-logger'
const logger = createLogger()
const store = createStore(
reducer,
applyMiddleware(logger)
)
再次使用時,可以看到
redux-thunk
用來搭建異步action
構(gòu)造器
npm i redux-thunk
安裝包
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { applyMiddleware, createStore } from 'redux'
import { createLogger } from 'redux-logger'
import thunk from 'redux-thunk'
import reducer from './reducer'
const logger = createLogger()
const store = createStore(
reducer,
applyMiddleware(thunk, logger)
)
function increment () {
return {type: 'INCREMENT'}
}
function asyncIncrement () {
return function (dispatch, getState) {
setTimeout(() => {
dispatch(increment())
}, 1000);
}
}
store.subscribe(() => {
render()
})
const Counter = (props) => {
return <div>
<h1>{props.value}</h1>
<button onClick={props.increment}>Increment</button>
<button onClick={props.asyncIncrement}>AsyncIncrement</button>
</div>
}
function render() {
ReactDOM.render(
<Counter
value={store.getState().increment}
increment={() => store.dispatch(increment())}
asyncIncrement={() => store.dispatch(asyncIncrement())}></Counter
>,
document.getElementById('root'));
}
render()
react和redux的連接
基本概念
react-redux
中組件分兩類:ui組件和容器組件
ui組件:只負責ui呈現(xiàn),沒邏輯
容器組件:管理數(shù)據(jù)和業(yè)務(wù)邏輯固该,有內(nèi)部狀態(tài)碑隆,使用redux的api
connect:用于從ui組件生成容器組件。小案例
目標:實現(xiàn)ui組件和容器組件的分離蹬音,實現(xiàn)點擊
+1
和點擊-1
的功能
create-react-app react-redux-demo
創(chuàng)建新項目
npm i redux react-redux
安裝包
文件結(jié)構(gòu)
CouterContainer.js
容器組件
import {connect} from 'react-redux'
import Counter from '../components/Counter'
const mapStateToProps = state => {
return {
value: state
}
}
const mapDispatchToProps = dispatch => {
return {
handleIncrement () {
dispatch({
type: 'INCREMENT'
})
},
handleDecrement () {
dispatch({
type: 'DECREMENT'
})
}
}
}
const CounterContainer = connect (
mapStateToProps,
mapDispatchToProps
) (Counter)
export default CounterContainer
Counter.js
ui組件
import React from 'react'
const Counter = (props) => {
return (
<div>
<h1>Counter Component</h1>
<h1>{props.value}</h1>
<button onClick={props.handleIncrement}>點擊+1</button>
<button onClick={props.handleDecrement}>點擊-1</button>
</div>
)
}
export default Counter
store > index.js
import {createStore} from 'redux'
function reducer (state = 100, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
const store = createStore(reducer)
export default store
index.js
引入Provider
和store
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { Provider } from 'react-redux'
import store from './store'
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>
, document.getElementById('root'));
app.js
引入容器組件
import React from 'react';
import CounterContainer from './containers/CouterContainer'
function App() {
return (
<div className="App">
<CounterContainer />
</div>
);
}
export default App;