React中的資料傳遞是從根節點開始,一級一級的向下傳遞, 相鄰較遠的元件之間的資料進行交流 就需要一個漫長的傳遞資料的過程, 為了解決這個問題, 便有了 Redux。
簡而言之, Redux 是一個狀態管理工具。 Rudux中有一個儲存資料的 store, 這個 store 是獨立於React元件之外的,每一個元件都可以直接拿到和修改store中的資料, 就不需要把資料傳來傳去了。
我們通過 一個 todoList的demo 來 弄懂 redux。
在開始之前, 首先 先使用 creat-react-app 初始化一個react專案。 為了方便使用, 專案中的元件直接使用 ant design 中元件。
首先, 安裝 redux
yarn add redux react-redux antd -D
複製程式碼
基礎
action
redux 中的 store 是隻讀的。 要想修改 store中的值,只能通過觸發 action 的方式來實現。 action 本質是一個 js 物件, action 內使用 type 欄位來表示 將要執行動作,可以在reducer中針對不同type執行相應的資料操作。
action 建立函式
action 建立函式 是 生成一個 action的方法。
export const ADD_TODO = 'ADD_TODO';
export const DEL_TODO = 'DEL_TODO';
export function addTodo(text) {
return {
type: ADD_TODO,
text
}
}
export function delTodo(index) {
return {
type: DEL_TODO,
index
}
}
複製程式碼
在 Rudux 中, 我們可以呼叫 store.dispatch() 方法, 觸發action。
在 React 中, 可以使用 react-redux 中的 connect() 方法來使用。 bindActionCreators() 可以自動把多個 action 建立函式 繫結到 dispatch() 方法上。
reducer
reducer 是一個純函式, 接受 舊的 state 和 action, 最後返回修改後的新 state。
注: 1. 不要直接修改 state。 更新state時, 可以採用 Object.assign({}, {...newState}) 的方式。 2. 在 default 情況下返回舊的 state。遇到未知的 action 時,一定要返回舊的 state。
reducer/list.js
import {
ADD_TODO,
DEL_TODO,
} from '../actions';
export function list(state = [], action) {
switch (action.type) {
case ADD_TODO: {
const { text } = action;
return [...state, text];
}
case DEL_TODO: {
const { index } = action;
let newState = [...state];
newState.splice(index, 1);
return newState;
}
default: {
return state;
}
}
}
複製程式碼
reducer/index.js
import { combineReducers } from 'redux'
import list from './list';
const app = combineReducers({
list,
})
export default app;
複製程式碼
store
store 就是我們當前專案的倉庫, 我們可以在 store儲存 我們專案中的用到的所有資料。 Redux 應用中只有一個單一的store。 如果需要對資料進行拆分, 可以把資料儲存在多個reducer中, 然後使用 combineReducers() 將多個reducer 合併為一個。
所有的容器元件都可以訪問 Redux Store, 一種方式是以 props 的形式傳入 容器元件中, 但這樣使用比較麻煩。 react 中 我們可以使用 , 這樣 我們所有的容器元件都可以訪問store,不需要通過 props 的方式進行傳遞。
在react中, 容器組減可以使用 connect() 方法訪問 store。使用 connect() 時, 使用mapStateToProps這個函式來指定當前 Redux store state 中需要對映到展示元件的 props 中的資料, 使用mapDispatchToProps() 方法接收 dispatch() 方法並返回期望注入到展示元件的 props 中的回撥方法。
import { connect } from 'react-redux';
import { addTodo } from 'action';
class Todo extends Component {
// ...
}
export default connect(
(state) => {
return {
list: state.list,
}
},
{
addTodo,
}
)(Todo)
複製程式碼
redux 除錯工具
redux有很多 第三方除錯工具, 我們這裡使用redux-devtools-extension.
使用步驟:
- 首先,在Chrome中安裝Redux Devtools擴充套件。
- 用npm或yarn安裝redux-devtools-extension包。
index.js
// ...
import { composeWithDevTools } from 'redux-devtools-extension';
const store = createStore(app, composeWithDevTools());
複製程式碼
程式碼
專案結構
├── README.md ├── package.json ├── src │ ├── App.css │ ├── App.js │ ├── App.test.js │ ├── actions │ │ └── index.js │ ├── components │ │ ├── Footer │ │ │ ├── index.js │ │ │ └── style.css │ │ └── Todo │ │ ├── index.js │ │ └── style.css │ ├── index.css │ ├── index.js │ ├── logo.svg │ ├── reducer │ │ ├── index.js │ │ └── list.js │ └── registerServiceWorker.js └── yarn.lock
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import app from './reducer';
import registerServiceWorker from './registerServiceWorker';
import App from './App';
import './index.css';
const store = createStore(app, composeWithDevTools());
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>
, document.getElementById('root'));
registerServiceWorker();
複製程式碼
App.js
import React, { Component } from 'react';
import Footer from './components/Footer';
import Todo from './components/Todo';
import logo from './logo.svg';
import './App.css';
class App extends Component {
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to React</h1>
</header>
<div className="App-intro">
<Footer />
<Todo />
</div>
</div>
);
}
}
export default App;
複製程式碼
action/index.js
export const ADD_TODO = 'ADD_TODO';
export const DEL_TODO = 'DEL_TODO';
export function addTodo(text) {
return {
type: ADD_TODO,
text
}
}
export function delTodo(index) {
return {
type: DEL_TODO,
index
}
}
複製程式碼
reducer/index.js
import { combineReducers } from 'redux'
import list from './list';
const app = combineReducers({
list,
})
export default app;
複製程式碼
reducer/list.js
import {
ADD_TODO,
DEL_TODO,
} from '../actions';
export default function list(state = [], action) {
switch (action.type) {
case ADD_TODO: {
const { text } = action;
return [...state, text];
}
case DEL_TODO: {
const { index } = action;
let newState = [...state];
newState.splice(index, 1);
return newState;
}
default: {
return state;
}
}
}
複製程式碼
component/Footer/index.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Input, message } from 'antd';
import { addTodo } from '../../actions';
import "./style.css";
const Search = Input.Search;
class Footer extends Component {
constructor(props) {
super(props);
this.state = {
inputValue: '',
}
}
handleChange = (e) => {
const inputValue = e.target.value;
this.setState({
inputValue
})
}
addTodo = () => {
const { inputValue } = this.state;
if (String(inputValue)) {
this.props.addTodo(inputValue);
this.setState({
inputValue: '',
})
} else {
message.error('todo is require');
}
}
render() {
const { inputValue } = this.state;
return (
<div className="footer">
<Search
value={inputValue}
placeholder="add a todo"
onChange={this.handleChange}
onSearch={this.addTodo}
enterButton="ADD"
/>
</div>
);
}
}
export default connect(
() => { return {} },{
addTodo
}
)(Footer);
複製程式碼
component/Todo/index.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Button } from 'antd';
import { delTodo } from '../../actions';
import "./style.css";
class Todo extends Component {
constructor(props) {
super(props);
}
delTodo = (index) => {
this.props.delTodo(index);
}
render() {
const { list } = this.props;
if (!list || !list.length) {
return null;
}
return (
<div className="todo">
{
list.map((item, index) => {
return (
<div key={index} className="todoItem">
{item}
<Button onClick={() => this.delTodo(index)}>Del</Button>
</div>
)
})
}
</div>
);
}
}
export default connect(
(state) => { return {
list: state.list,
} },{
delTodo
}
)(Todo);
複製程式碼