1、UI 组件和容器组件
UI 组件负责容器的渲染,容器组件负责页面的逻辑 ;
将之前的 TodoList.js 拆分成 UI 组件和容器组件,
TodoListUI.js :
import React, { Component } from 'react';
import { Input, Button, List } from 'antd';
class TodoListUI extends Component {
render() {
return (
<div style={{marginTop: '10px', marginLeft: '10px'}}>
<div>
<Input
placeholder="todo info"
style={{width: '300px', marginRight: '10px'}}
value = {this.props.inputValue}
onChange={this.props.handleInputChange}
/>
<Button
type="primary"
onClick={this.props.handleBtnClick}
>
提交
</Button>
</div>
<List
style={{marginTop: '10px', width: '300px'}}
bordered
dataSource={this.props.list}
renderItem={
(item, index) => <List.Item onClick={(index) => this.props.handleDeleteItem(index)}> {item}</List.Item>
}
/>
</div>
)
}handleDeleteItem
}
export default TodoListUI;
TodoList.js :
import React, { Component } from 'react';
import 'antd/dist/antd.css';
import store from './store/index'; //引入store
import { getHandleInputChangeAction, getHandleBtnClick, getHandleDeleteItem } from './store/actionCreators';
import TodoListUI from './TodoListUI';
class TodoList extends Component {
constructor(props) {
super(props);
this.state = store.getState();
this.handleInputChange = this.handleInputChange.bind(this);
this.handleStoreChange = this.handleStoreChange.bind(this);
this.handleBtnClick=this.handleBtnClick.bind(this);
this.handleDeleteItem=this.handleDeleteItem.bind(this);
store.subscribe(this.handleStoreChange); //只要store发生改变就执行handleStoreChange这个函数
}
render() {
return (
<TodoListUI
inputValue={this.state.inputValue}
handleInputChange={this.handleInputChange}
handleBtnClick={this.handleBtnClick}
handleDeleteItem={this.handleDeleteItem}
list={this.state.list}
/>
)
}
handleInputChange(e) {
const action = getHandleInputChangeAction(e.target.value);
store.dispatch(action);
}
handleStoreChange() {
this.setState(store.getState()); //当我感知到store的数据发生变化的时候,就去调用store.getState方法,从store里重新取一次数据,然后通过this.setState替换掉当前组件的数据
}
handleBtnClick() {
const action = getHandleBtnClick();
store.dispatch(action);
}
handleDeleteItem(index) {
const action = getHandleDeleteItem(index);
store.dispatch(action);
}
}
export default TodoList;
2、无状态组件
当我们一个组件只有 render 函数的时候,就可以用一个无状态组件来定义它。
优势:它的性能比较高,因为它就是一个函数,而之前的 TodoListUI 是一个类,类生成对象的过程中需要经过一些生命周期函数,它执行的东西远比执行一个函数要多得多。
使用场景:一般用在 UI 组件中 。
TodoListUI.js :
import React from 'react';
import { Input, Button, List } from 'antd';
//无状态组件
const TodoListUI = (props) => {
return (
<div style={{marginTop: '10px', marginLeft: '10px'}}>
<div>
<Input
placeholder="todo info"
style={{width: '300px', marginRight: '10px'}}
value = {props.inputValue}
onChange={props.handleInputChange}
/>
<Button
type="primary"
onClick={props.handleBtnClick}
>
提交
</Button>
</div>
<List
style={{marginTop: '10px', width: '300px'}}
bordered
dataSource={props.list}
renderItem={
(item, index) => <List.Item onClick={(index) => props.handleDeleteItem(index)}> {item}</List.Item>
}
/>
</div>
)
}
export default TodoListUI;
3、Redux 中发送异步请求获取数据
mockdata.js :
import Mock from 'mockjs';
const data = ['吃饭', '睡觉', '大幺幺'];
Mock.mock('/list', {
data
})
TodoList.js 引入 mockjs 后添加异步请求代码:
componentDidMount() {
axios.get('/list')
.then(res => {
const data = res.data.data;
const action = getAjaxData(data);
store.dispatch(action);
});
}
接着给添加 redux 中的代码,告诉 store 将 data 赋值给 list ,这样一开始的 item 显示的就是 ajax 获取的数据了。
4、 使用 Redux-thunk 中间件实现 ajax 数据请求 ;
redux-thunk 这个中间件可以使我们把复杂的逻辑移到 action 中去管理。
同时用两个以上的中间件时 应该这个定义store ,store 下的 index.js :
/*创建一个store*/
import { createStore, applyMiddleware, compose } from 'redux'; //引入createStore
import reducer from './reducer';
import thunk from 'redux-thunk';
//同时用两个以上的中间件
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
const enhancer = composeEnhancers(
applyMiddleware(thunk),
);
const store = createStore( reducer, enhancer );
export default store;
actionCreators.js :
import { CHANGE_INPUT_VALUE, ADD_TODO_ITEM, DELETE_TODO_ITEM, AJAX_DATA } from './actionTypes';
import axios from 'axios';
export const getHandleInputChangeAction = (value) => ({
type: CHANGE_INPUT_VALUE,
value
})
export const getHandleBtnClick = () => ({
type: ADD_TODO_ITEM
})
export const getHandleDeleteItem = (index) => ({
type: DELETE_TODO_ITEM,
index
})
export const getAjaxData = (data) => ({
type: AJAX_DATA,
data
})
//异步请求
export const getTodoList = () => {
//使用了 redux-thunk 后就能返回一个函数
return (dispatch) => {
axios.get('/list')
.then(res => {
const data = res.data.data;
const action = getAjaxData(data);
dispatch(action);
});
}
}
TodoList.js :
componentDidMount() {
const action = getTodoList();
store.dispatch(action); //当store判断到action不是一个对象的时候,它就会去执行action这个函数
}
5、什么是Redux 中间件
中间指的是 action 和 store 之间,实际上中间件就是对 dispatch 方法的一个封装,最原始的 dispatch 方法接收到一个对象之后就会把这个对象传递给 store ,这个就是没有中间件的情况。
当我们对 dispatch 方法做了一个升级之后,比如说使用了 redux-thunk ,此时调用 dispatch 方法,给 dispatch 方法传递的是一个对象的话,那么这个 dispatch 方法又会把这个对象直接传给 store ,这和原始的 dispatch 方法没有区别。假设传递的是一个函数的话,他不会把这个函数直接传递给 store ,他会让这个函数先执行,执行完了之后这个函数再去调用 store 。
6、Redux-saga中间件使用入门
redux-saga 和 redux-thunk 一样,都是处理复杂的逻辑或者异步操作。
saga 一般都会单独创建一个 js 文件来管理。
有了 redux-saga 之后,除了在 reducer 里面可以接收 action ,saga.js 也能接收 action 。
- 目录结构
- TodoList.js
componentDidMount() { //给sage发action
const action = getInitialData();
store.dispatch(action);
}
- sage.js
import { takeEvery, put } from 'redux-saga/effects';
import { GET_INITIAL_DATA } from './actionTypes';
import { getAjaxData } from './actionCreator';
import axios from 'axios';
//put 就是来代替store.dispatch方法的
//takeEvery 是 redux-saga提供的一个方法
function* getInitialData() {
try {
const res = yield axios.get('/list');
const action = getAjaxData(res.data.data);
yield put(action);
} catch(e) {
console.log('网络请求失败');
}
}
function* mySaga() {
yield takeEvery(GET_INITIAL_DATA, getInitialData);
//意思就是只要捕捉到GET_INITIAL_DATA这个action类型,就去执行getInitialData这个方法
}
export default mySaga;
此时就能正常地获取数据了。
7、React-Redux 的使用
React-Redux 是一个第三方的模块,可以帮助我们在 React 中更方便地使用 Redux 。
无状态组件可以有效地提升代码的性能,因为里面没有任何的生命周期函数,同时也不会生成真正的组件实例。
- 项目目录结构:
- TodoList.js:
import React from 'react';
import 'antd/dist/antd.css';
import { Input, Button, List } from 'antd';
import { connect } from 'react-redux'; //因为index.js中的Provider将store做了连接,使内部的组件都能很方便地获取store。就是通过connect这个方法来获取store里面地数据的
import { getChangeInputValue, getAddItem, getDelItem } from './store/actionCreater';
const TodoList = (props) => {
const { inputValue, list, changeInputValue, handleClick, handleDeleteItem } = props;
return (
<div style={{ textAlign: "center" }}>
<Input
placeholder="Basic usage"
style={{ width: 300, margin: 10 }}
value={inputValue}
onChange={changeInputValue}
/>
<Button
type="primary"
onClick={handleClick}
>
提交
</Button>
<List
style={{ width: 400, textAlign: "center", margin: '10px auto' }}
bordered
dataSource={list}
renderItem={item => <List.Item onClick={handleDeleteItem.bind(this, item.id)}>{item.content}</List.Item>}
/>
</div>
)
}
const mapStateToProps = (state) => { //state是store里面的数据
return {
inputValue: state.inputValue,
list: state.list
}
}
const mapDispatchToProps = (dispatch) => { //对store的数据做修改
return {
changeInputValue(e) {
const action = getChangeInputValue(e.target.value);
dispatch(action);
},
handleClick() {
const action = getAddItem();
dispatch(action);
},
handleDeleteItem(id) {
const action = getDelItem(id);
dispatch(action);
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(TodoList); //意思就是让我的todolist这个组件和store做连接, 连接规则在 mapStateToProps 里面
//之前我们都是直接导出 TodoList 这个组件,现在我们导出的是connect这个方法。可以这么理解:现在 TodoList是一个UI组件,当我们用connect把这个UI组件做一些业务逻辑相结合的时候,然会的内容实际上就是一个容器组件。
- index.js :
import React from 'react';
import ReactDOM from 'react-dom';
import TodoList from './TodoList';
import { Provider } from 'react-redux';
import store from './store/index';
const App = (
<Provider store={store}> {/*其实 Provider 就是一个组件,Provider意思就是我连接了store,那么Provider里面所有的组件就有能力获取到store里面的内容了*/}
<TodoList />
</Provider>
)
ReactDOM.render(App, document.getElementById('root'));
别的文件内容和之前相同。