1.UI组件与容器组件的拆分
UI组件业界称为傻瓜组件[只负责页面显示],容器组件业界称为聪明组件[不管UI长成什么样子,关注业务逻辑]
UI组件负责页面的渲染,容器组件负责页面逻辑
将TodoList组件拆分成TodoList的容器组件和TodoListUI的UI组件
示例代码:
//[TodoList.js] import React ,{Component} from 'react'; import store from './store/index'; //也可以直接写成 import store from './store' 会默认直接找index.js import {getInputChangeAction,getAddItemAction,getDeleteItemAction} from './store/actionCreators' import TodoListUI from './TodoListUI'; class Todolist extends Component{ constructor(props) { super(props); //console.log(store.getState()); //store的方法,用于获取store中的数据 this.state=store.getState(); console.log(this.state); this.handleInputChange = this.handleInputChange.bind(this); this.handleStoreChange = this.handleStoreChange.bind(this); this.handleButtonClick = this.handleButtonClick.bind(this); this.handleItemDelete = this.handleItemDelete.bind(this); store.subscribe(this.handleStoreChange);//将handleStoreChange函数订阅给store,每当store中的数据发生变化的时候将调用此函数 } render(){ return( <TodoListUI inputValue = {this.state.inputValue} handleInputChange = {this.handleInputChange} handleButtonClick = {this.handleButtonClick} list = {this.state.list} handleItemDelete = {this.handleItemDelete} /> ) } handleInputChange(e){ //创建action const action = getInputChangeAction(e.target.value); store.dispatch(action); //将action传递给store } handleStoreChange(){ this.setState(store.getState());//当store发生变化时,则调用此方法。用store中的数据替换当前组件的state数据。 } handleButtonClick(){ const action = getAddItemAction(); store.dispatch(action); } handleItemDelete(index){ const action=getDeleteItemAction(index); store.dispatch(action); } } export default Todolist; //[TodoListUI.js] import React,{Component} from 'react'; import 'antd/dist/antd.css';//antd是全局变量 import {Input,Button,List} from 'antd'; class TodoListUI extends Component{ render(){ return( <div style={{marginTop:'10px',marginLeft:"10px"}}> <div> {/*输入框组件*/} <Input placeholder="todoInfo" onChange={this.props.handleInputChange} style={{width:'300px',marginRight:'10px'}} value={this.props.inputValue}/> {/*按钮组件*/} <Button type="primary" onClick={this.props.handleButtonClick}>提交</Button> {/*列表组件 header列表头 footer列表底 bordered列表有边框 dataSource列表渲染的数据资源 renderItem怎么渲染,dataSource中的每一个数据都会调用renderItem中的函数进行渲染,将每一条数据渲染成List.Item的组件,数据的内容就是dataSource中每一项数据的内容 */} <List header={<div>header</div>} footer={<div>footer</div>} bordered dataSource={this.props.list} renderItem={(item,index)=>(<List.Item onClick={(index) => {this.props.handleItemDelete(index)}}>{item}</List.Item>)} style={{marginTop:'10px',width:'300px'}} /> </div> </div> ) } } export default TodoListUI;
2.无状态组件
什么是无状态组件?
- 当组件只有render函数时,可以用无状态组件替换该组件
- 无状态组件是一个函数
- 无状态组件的性能比较高,因为就是一个函数;而普通的组件是一个类,需要执行所有生命周期函数
- 适用时刻:一般当定义UI组件时,只做页面渲染而不做逻辑操作时,就可以将该组件用无状态组件替换
示例代码:
//[替换前TodoListUI.js] import React,{Component} from 'react'; import 'antd/dist/antd.css';//antd是全局变量 import {Input,Button,List} from 'antd'; class TodoListUI extends Component{ render(){ return( <div style={{marginTop:'10px',marginLeft:"10px"}}> <div> {/*输入框组件*/} <Input placeholder="todoInfo" onChange={this.props.handleInputChange} style={{width:'300px',marginRight:'10px'}} value={this.props.inputValue}/> {/*按钮组件*/} <Button type="primary" onClick={this.props.handleButtonClick}>提交</Button> {/*列表组件 header列表头 footer列表底 bordered列表有边框 dataSource列表渲染的数据资源 renderItem怎么渲染,dataSource中的每一个数据都会调用renderItem中的函数进行渲染,将每一条数据渲染成List.Item的组件,数据的内容就是dataSource中每一项数据的内容 */} <List header={<div>header</div>} footer={<div>footer</div>} bordered dataSource={this.props.list} renderItem={(item,index)=>(<List.Item onClick={(index) => {this.props.handleItemDelete(index)}}>{item}</List.Item>)} style={{marginTop:'10px',width:'300px'}} /> </div> </div> ) } } export default TodoListUI; //[替换后TodoListUI.js] import React from 'react'; import 'antd/dist/antd.css';//antd是全局变量 import {Input,Button,List} from 'antd'; const TodoListUI = (props)=>{ return( <div style={{marginTop:'10px',marginLeft:"10px"}}> <div> {/*输入框组件*/} <Input placeholder="todoInfo" onChange={props.handleInputChange} style={{width:'300px',marginRight:'10px'}} value={props.inputValue}/> {/*按钮组件*/} <Button type="primary" onClick={props.handleButtonClick}>提交</Button> {/*列表组件 header列表头 footer列表底 bordered列表有边框 dataSource列表渲染的数据资源 renderItem怎么渲染,dataSource中的每一个数据都会调用renderItem中的函数进行渲染,将每一条数据渲染成List.Item的组件,数据的内容就是dataSource中每一项数据的内容 */} <List header={<div>header</div>} footer={<div>footer</div>} bordered dataSource={props.list} renderItem={(item,index)=>(<List.Item onClick={() => {props.handleItemDelete(index)}}>{item}</List.Item>)} style={{marginTop:'10px',width:'300px'}} /> </div> </div> ) }; export default TodoListUI;
3.Redux中发送异步请求获取数据
利用Charles工具设置拦截的请求:Tools->Map local->Add并添加拦截的路径和返回的数据->OK
在项目内安装axios依赖:yarn add axios
在TodoList组件中引入axios
import axios from 'axios';
在TodoList组件的生命周期componentDidMount中利用axios发送ajax请求获取数据
//引入action事件 import {initListAction} from './store/actionCreators' //[TodoList.js部分代码] componentDidMount(){ axios.get("/list.json") .then((res)=>{ const data = res.data; const action = initListAction(data); store.dispatch(action); }); }
在actionTypes.js中添加action类型常量
//[store/actionTypes.js] export const INIT_LIST_ACTION = "init_list_action";
在actionCreators.js中添加action传递信息
//[store/actionCreators.js] //引入action类型常量 import {INIT_LIST_ACTION} from './actionTypes'; //添加action事件 export const initListAction = (data)=>({ type:INIT_LIST_ACTION, data })
在reducer.js中添加对应action的处理方法
//引入action类型常量 import {INIT_LIST_ACTION} from './actionTypes'; //[store/reducer.js]中的默认导出方法中添加处理内容 else if(action.type === INIT_LIST_ACTION){ const newState = JSON.parse(JSON.stringify(state)); newState.list=[...newState,...action.data]; return newState; }
4.使用Redux-thunk中间件进行ajax请求发送
可以利用Redux-thunk中间件将异步请求和复杂逻辑放到action中处理
Redux-thunk是redux的中间件
利用Redux-thunk进行代码的编写
安装Redux-thunk:yarn add redux-thunk
使用Redux-thunk
- store的index.js要在redux中引入applyMiddleware,从而保证使用中间件;在redux-thunk引入thunk模块
//[store/index.js] import {applyMiddleware} from 'redux'; import thunk from 'redux-thunk';
- 在createStore的第二个参数使用thunk中间件
//[store/index.js] const store = createStore( reducer, applyMiddleware(thunk), );
thunk是redux的中间件
- 若想同时使用Redux-devTools-Extension和thunk,则需要对store做如下配置
import {createStore,applyMiddleware,compose} from 'redux'//引入compose import reducer from './reducer' //将笔记本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;
- 使用redux-thunk可以将返回值为函数的内容发送给store。store发现action是一个函数时,会自动执行这个函数。action的函数的第一个参数是dispatch方法,故可以在action的函数中创建action对象,并利用dispatch(action对象)将action对象发送给store。此时store得到的是action对象则会去调用reducer对数据进行处理。
//[TodoList.js]使用redux-thunk可以将返回值为函数的内容发送给store import {getTodoList} from './store/actionCreators' componentDidMount(){ const action=getTodoList(); store.dispatch(action);//当调用store.dispatch将action发送给store时,action会被自动执行 } //[store/actionCreators.js]store发现action是一个函数时,会自动执行这个函数。action的函数的第一个参数是dispatch方法,故可以在action的函数中创建action对象,并利用dispatch(action对象)将action对象发送给store。 export const getTodoList= ()=>{ return (dispatch)=>{ axios.get("/list.json") .then((res)=>{ const data = res.data; const action = initListAction(data); dispatch(action); }); } } //此时store得到的是action对象则会去调用reducer对数据进行处理。
- 总结:借助redux-thunk,我们将异步请求放到actionCreators中进行管理。自动化测试时可以方便的去测试对应方法。
5.到底什么是Redux中间件?
Redux标准流程
Redux中间件:是Action和Store的中间,对Dispatch方法的一个升级。如果Dispatch传递的是对象则直接传递给store;如果传递的是函数,则执行对应的函数。
6.Redux-saga中间件的使用
安装Redux-saga:yarn add redux-saga
运行流程
在store中配置redux-saga
引入createSagaMiddleware:
import createSagaMiddleware from 'redux-saga';
创建sagaMiddleware:
const sagaMiddleware = createSagaMiddleware();
通过applyMiddleware去使用中间件:
applyMiddleware(sagaMiddleware);
创建文件sagas.js
在store中引入sagas.js并通过sagaMIddleware去运行这个文件:
import todoSagas from './sagas'; sagaMiddleware.run(todoSagas);
//[store/index.js]完整代码 import {createStore,applyMiddleware,compose} from 'redux' import reducer from './reducer' //将笔记本引入进来 import createSagaMiddleware from 'redux-saga'; import todoSagas from './sagas'; // create the saga middleware const sagaMiddleware = createSagaMiddleware() const composeEnhancers =window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose; const enhancer = composeEnhancers( applyMiddleware(sagaMiddleware), ); //创建数据的公共存储仓库 const store = createStore( reducer, enhancer ); sagaMiddleware.run(todoSagas); export default store;
编写TodoList.js和actionCreators.js
//TodoList.js componentDidMount(){ const action = getInitList(); store.dispatch(action); } //actionCreators.js export const getInitList = ()=>({ type:GET_INIT_LIST })
编写sagas.js
- sagas需要导出一个generator函数,在函数中写相关逻辑【相关含义请参加注释】
//存放异步逻辑 import {takeEvery,put} from 'redux-saga/effects';//takeEvery是redux提供的方法 import {GET_INIT_LIST} from './actionTypes'; import {initListAction} from './actionCreators'; import axios from 'axios'; function* getInitList(){ const res = yield axios.get("/list.json");//取数据 const action = initListAction(res.data);//将数据的结果创建一个action yield put(action);//将action通过put方法派发给store——注意:put是redux-saga特有的派发函数。 } //saga的导出必须是generator函数 function* mySaga() { yield takeEvery(GET_INIT_LIST,getInitList);//表示捕捉到一个action类型为GET_INIT_LIST时,就会执行getInitList方法 } export default mySaga;
store接收到sagas通过put过来的action,就转交给reducer进行判断,判断是GET_INIT_LIST类型的action会执行对应代码,从而返回新state,store更新state,页面重新渲染。
总结:
- redux-saga的使用是在组件编写完actionCreators会在store给reducer的同时,还给saga的导出的函数进行处理,在saga中通过takeEvery如果匹配上action类型则会执行对应函数。
- Redux-saga适合大型项目,拥有大量API;Redux-thunk没有API,只是帮助我们在action中返回的不指是对象还可以是方法,更加方便。
- redux中间件是action和store之间的中间件,是dispatcher方法的升级。
7.React-Redux的使用
什么是React-Redux?
- 是第三方模块,帮助在React中更方便的使用Redux
安装React-Redux:yarn add react-redux
重点:
- Provider组件(又称:提供器)是react-redux第一个核心组件,可以帮助我们把store提供给Provider内部的每一个组件
- 子组件内部通过connect方法与store做连接。connect(连接规则mapStateToProps,连接规则mapDispatcherToProps)(组件名)表示该组件要和store进行连接,把store里的数据和store里的数据的关系在mapStateToProps中列清楚;把组件里props如何对store里的数据做修改和store里的dispatch做关联,通过mapDispatchToProps函数返回的对象。
示例代码[详情信息参见注释]:
//[index.js] import React from 'react'; import ReactDOM from 'react-dom'; import TodoList from './TodoList'; import {Provider} from 'react-redux';//Provider就是一个组件,提供器连接了store,则提供器内部的所有组件都有能力获取store的内容 import store from './store';//引入store const App=( <Provider store={store}> <TodoList/> </Provider> ); ReactDOM.render(App, document.getElementById('root')); //[TodoList.js] import React,{Component} from 'react'; import {connect} from 'react-redux';//使组件和store连接 class TodoList extends Component{ render(){ return( <div> <div> {/*this.props.inputValue根据mapStateToProps方法获得的值;this.props.changeInputValue通过mapDispatchToProps方法获得的函数*/} <input value={this.props.inputValue} onChange={this.props.changeInputValue}/> <button>提交</button> </div> <ul> <li>Dell</li> <li>jack</li> </ul> </div> ) } } //mapStateToProps表示怎么映射?将Store中的数据映射给组件成为Props值,state参数表示store中的数据 const mapStateToProps = (state)=>{ return{ inputValue:state.inputValue } } //mapDispatchToProps表示将store的dispatch方法挂载到props上,通过this.props来调用dispatch方法 const mapDispatchToProps=(dispatch)=>{ return{ changeInputValue(e){ const action = { type:'change_input_value', value:e.target.value } dispatch(action); } } } export default connect(mapStateToProps,mapDispatchToProps)(TodoList);//让TodoList组件和store做连接,store中的数据会映射到props中,同时如果想对store做修改,可以通过connect第二个参数将dispatch方法映射到props上 //[store/index.js] import {createStore} from 'redux'; import reducer from './reducer'; const store = createStore(reducer); export default store; //[store/reducer.js] const defaultState = { inputValue:'hello world', list:[] } export default (state=defaultState,action)=>{ if(action.type === "change_input_value"){ const newState = JSON.parse(JSON.stringify(state));//对state进行深拷贝 newState.inputValue = action.value; return newState; } return state; }
8.React-Redux的使用
示例代码[详情请参见注释]:
//[index.js] import React from 'react'; import ReactDOM from 'react-dom'; import TodoList from './TodoList'; import {Provider} from 'react-redux';//Provider就是一个组件,提供器连接了store,则提供器内部的所有组件都有能力获取store的内容 import store from './store';//引入store const App=( <Provider store={store}> <TodoList/> </Provider> ); ReactDOM.render(App, document.getElementById('root')); //[TodoList.js] import React from 'react'; import {connect} from 'react-redux';//使组件和store连接 //由于TodoList组件只进行显示而没有业务逻辑,且只有render方法,故既是UI组件又是无状态组件 const TodoList = (props)=>{ const {inputValue,list,changeInputValue,handleClick} = props;//通过解构赋值定义变量 return( <div> <div> {/*this.props.inputValue根据mapStateToProps方法获得的;this.props.changeInputValue通过mapDispatchToProps方法获得*/} <input value={inputValue} onChange={changeInputValue}/> <button onClick={handleClick}>提交</button> </div> <ul> { list.map((item,index)=>{ return( <li key={index} onClick={()=>{props.handleDelete(index)}}>{item}</li> ) }) } </ul> </div> ) } //mapStateToProps表示怎么映射?将Store中的数据映射给组件成为Props值,state参数表示store中的数据 const mapStateToProps = (state)=>{ return{ inputValue:state.inputValue, list:state.list } } //mapDispatchToProps表示将store的dispatch方法挂载到props上,通过this.props来调用dispatch方法 const mapDispatchToProps=(dispatch)=>{ return{ changeInputValue(e){ const action = { type:'change_input_value', value:e.target.value } dispatch(action); }, handleClick(){ const action={ type:'add_item' } dispatch(action); }, handleDelete(index){ const action={ type:'delete_item', index:index }; dispatch(action); } } } //理解connect:TodoList本身是UI组件,只做显示。但是connect方法将连接规则和组件相结合,返回的结果是一个容器组件(有业务逻辑和UI)。 export default connect(mapStateToProps,mapDispatchToProps)(TodoList);//让TodoList组件和store做连接,store中的数据会映射到props中,同时如果想对store做修改,可以通过connect第二个参数将dispatch方法映射到props上 //[store/index.js] import {createStore} from 'redux'; import reducer from './reducer'; const store = createStore(reducer); export default store; //[store/reducer.js] const defaultState = { inputValue:'', list:[] } export default (state=defaultState,action)=>{ if(action.type === "change_input_value"){ const newState = JSON.parse(JSON.stringify(state));//对state进行深拷贝 newState.inputValue = action.value; return newState; }else if(action.type === "add_item"){ const newState = JSON.parse(JSON.stringify(state)); newState.list.push(newState.inputValue); newState.inputValue=''; return newState; }else if(action.type === "delete_item"){ const newState = JSON.parse(JSON.stringify(state)); newState.list.splice(action.index,1); return newState; } return state; }
注意点:
- 理解connect:TodoList本身是UI组件,只做显示,故可以转换成无状态组件从而提升性能。但是connect方法将连接规则和组件相结合,返回的结果是一个容器组件(有业务逻辑和UI)。