Redux相关 && React和Redux结合使用
Redux相关
随着应用程序的规模和范围的增长,管理共享数据变得很困难。Redux被定义为“JavaScript应用程序的可预测state的容器”,有助于确保应用程序可预测地工作,更易于测试。接下来我们一起看看,Redux存储、操作、缩减器、中间件管理整个应用程序数据。
Redux是一个状态管理框架,可以与许多不同的web技术一起使用,包括React。在Redux中,只有一个状态对象负责应用程序的整个state。这意味着,如果有一个包含10个组件的React应用程序,并且每个组件都有自己的本地state,则应用程序的整个state将由Redux存储中的单个状态对象定义。当涉及到应用程序state时,Redux存储(Redux store)是唯一的真值来源。这也意味着,任何时候我们的应用程序的任何部分想要更新state,它都必须通过Redux store进行更新。单向数据流使我们更容易在应用程序中跟踪状态管理。
Redux store是一个保存和管理应用程序state的对象。Redux对象上有一个名为createStore()的方法,用于创建Redux store;createStore()方法将reducer函数作为必需参数。下面声明一个“store”变量并为其赋上createStore()方法,将reducer作为参数传入:
const reducer = (state = 5) => { return state; } let store= Redux.createStore(reducer);
Redux store对象提供了几种方法,允许我们与它进行交互。如,可以使用getState()方法检索Redux store对象中保存的当前state。上面的例子可以更简洁地重写:
const store = Redux.createStore( (state = 5) => state ); /*使用store.getState()从store中检索state,并将其分配给一个新变量currentState*/ let currentState=store.getState();
由于Redux是一个状态管理框架,更新state是其核心任务之一。在Redux中,所有state更新都由发送action触发。一个动作(action )只是一个JavaScript对象,它包含有关已发生的动作事件的信息。Redux store接收这些动作对象,然后相应地更新其state。有时,Redux动作也会携带一些数据。例如,用户登录后,该动作会携带用户名。虽然数据是可选的,但action必须带有“type”属性,该属性指定发生的动作的“type”。将Redux动作视为通信员,通信员将应用程序中发生的事件信息传递到Redux store,然后,store根据发生的动作进行更新state的业务。
编写Redux action就像定义一个带有type属性的对象一样简单:
// 定义一个action: let action={ type:'LOGIN' } /*声明一个对象“action”,并将属性“type”设置为字符串“LOGIN”*/
创建action后,下一步是将action发送到Redux store,以便它可以更新其state。 这一步通过定义action creator来完成,action creator只是一个返回action的JavaScript函数,即action creator创建表示动作事件的对象:
//定义action: const action = { type: 'LOGIN' } //定义action creator: function actionCreator(){ return action; }
dispatch方法用于将action发送到Redux store。调用store.dispatch()并传递从action creator返回的值,将action发送回store。即action creator返回一个带有type属性的发生特定动作的对象,然后,store.dispatch()方法将action对象发送到Redux store。在以上示例代码的基础上,下面2行代码是等效的,都发送了type为LOGIN的动作:
store.dispatch(actionCreator());
store.dispatch({ type: 'LOGIN' });
看个具体例子:
//定义Redux store const store = Redux.createStore( (state = {login: false}) => state ); //定义action creator const loginAction = () => { return { type: 'LOGIN' } }; // 把loginAction()创建的action发送到Redux store: store.dispatch(loginAction());
在创建并发送action之后,Redux store需要知道如何响应该action,这时reducer函数就起作用了。Redux中的Reducer负责响应action而进行的state修改。reducer接受state和action作为参数,总是返回一个新的state,这是reducer的唯一职能。它没有副作用——从不调用API端点,也不会有任何隐藏的惊喜,reducer只是一个纯函数,它接受state和action,然后返回新的state。
Redux的另一个关键原则是state是只读的。换句话说,reducer函数必须始终返回state的新副本,而不能直接修改state。Redux并没有强制执行state不可变性,但是,我们有责任在reducer函数的代码中强制执行它。
const defaultState = { login: false }; const reducer = (state = defaultState, action) => {
//注意,当前state和发送的action已经传给reducer了,所以可以使用action.type直接访问action的type。 //当action的type属性值为'LOGIN'时,返回将login设置为true的新state对象 if(action.type==='LOGIN') { return {login:true};//记住reducer返回的是新state,而state是一个包含键值对的对象!! } else { return state; } }; const store = Redux.createStore(reducer); const loginAction = () => { return { type: 'LOGIN' } };
我们可以让Redux store处理action的多个type。通过在reducer中使用JavaScript 的switch语句来响应不同的action事件。这是编写Redux reducer的标准模式,switch语句切换action。下例通过reducer函数处理多个身份验证action:
const defaultState = { authenticated: false }; //reducer函数 const authReducer = (state = defaultState, action) => { switch(action.type){ case 'LOGIN': return {authenticated: true}; case 'LOGOUT': return {authenticated: false}; default: return state; } }; //Redux store const store = Redux.createStore(authReducer); //用户登录的action const loginUser = () => { return { type: 'LOGIN' } }; //用户注销的action const logoutUser = () => { return { type: 'LOGOUT' } };
使用Redux时的一个常见做法是将action的type指定为只读常量,然后在使用这些常量的任何地方引用这些常量:
//Redux通常习惯用大写字母写常量。 const LOGIN='LOGIN'; const LOGOUT='LOGOUT'; const defaultState = { authenticated: false }; const authReducer = (state = defaultState, action) => { switch (action.type) { case LOGIN: return { authenticated: true } case LOGOUT: return { authenticated: false } default: return state; } }; const store = Redux.createStore(authReducer); const loginUser = () => { return { type: LOGIN } }; const logoutUser = () => { return { type: LOGOUT } };
store.subscribe()允许我们向store订阅侦听器函数,store.subscribe()接受一个我们写的函数作为参数,每当针对store发送action时,都会调用这个函数。此方法的一个简单用途是向store订阅一个函数,该函数只需在每次收到action并更新store时记录一条消息。看个例子:
const ADD = 'ADD'; const reducer = (state = 0, action) => { switch(action.type) { case ADD: return state + 1; default: return state; } }; const store = Redux.createStore(reducer); //全局变量count: let count = 0; /*编写一个回调函数,将此函数传递给store.subscribe()方法,使得每当store接收到一个action时就递增全局变量count*/ function sum(){ count+=1; }; /*每当store接收到一个action时,subscribe方法就会执行sum函数一次!!!*/ store.subscribe(sum); store.dispatch({type: ADD}); console.log(count);//1 store.dispatch({type: ADD}); console.log(count);//2 store.dispatch({type: ADD}); console.log(count);//3 store.dispatch({type: ADD}); console.log(count);//4 /*连续调用store.dispatch()方法4次,每次都直接传入一个action对象:{type: ADD}*/
记住Redux的第一个原则:所有应用程序的state都保存在store的state对象中。因此,Redux提供了reducer组合,作为复杂state模型的解决方案。即定义多个reducer来处理应用程序的state的不同部分,然后将这些reducer组合到一个根reducer中,最后将根reducer传递到Redux createStore()方法中。
为了让我们将多个reducer组合在一起,Redux提供了combineReducers()方法。此方法接受对象作为参数,此对象中定义了将键与指定的reducer函数联系到一起的属性。键名会被Redux用作相关的state段的名称。通常,当应用程序的每个state在某种程度上不同或唯一时,分别为它们创建一个reducer是一个很好的做法。例如,在具有用户身份验证的“note-taking”应用程序中,一个reducer处理身份验证,而另一个reducer则处理用户提交的文本和笔记,对于这样的应用程序,我们可以这样编写combineReducers()方法:
const rootReducer = Redux.combineReducers({
auth: authenticationReducer,
notes: notesReducer
});
上例中,notes键将包涵与笔记相关、由notesReducer处理的所有state。如此即可组合多个reducer以管理更复杂的应用程序state。本例中,Redux store中保存的state将会是一个包含“auth”和“notes”属性的对象。
再看个例子:
const INCREMENT = 'INCREMENT'; const DECREMENT = 'DECREMENT'; const counterReducer = (state = 0, action) => { switch(action.type) { case INCREMENT: return state + 1; case DECREMENT: return state - 1; default: return state; } }; const LOGIN = 'LOGIN'; const LOGOUT = 'LOGOUT'; const authReducer = (state = {authenticated: false}, action) => { switch(action.type) { case LOGIN: return { authenticated: true } case LOGOUT: return { authenticated: false } default: return state; } }; //通过Redux.combineReducers()将子reducer函数结合起来,定义根reducer const rootReducer =Redux.combineReducers({ count:counterReducer, auth:authReducer }); //将根reducer传给Redux.createStore()方法,创建Redux store const store = Redux.createStore(rootReducer);
发送到Redux store的action数据不止限于type,还可以发送特定数据:
const ADD_NOTE = 'ADD_NOTE'; const notesReducer = (state = 'Initial State', action) => { switch(action.type) { case ADD_NOTE: return action.text;//返回所传入的action的text属性值作为新的state default: return state; } }; const addNoteText = (note) => { //当调用action创建器时,将传入可以供此对象访问的note,text属性就有了值。 return { type:ADD_NOTE, text:note } }; //Redux store const store = Redux.createStore(notesReducer); console.log(store.getState());//Initial State store.dispatch(addNoteText('Hello!'));//向Redux store发送action(调用action创建器) console.log(store.getState());//Hello!
异步操作(asynchronous action)是web开发中不可避免的一部分。有时我们需要在Redux应用程序中调用异步端点,那么如何处理这类请求呢?Redux有一个专门为此设计的中间件(middleware ),称为Redux Thunk中间件。下面简要说明如何在Redux中使用此功能。
要使用Redux Thunk中间件,可以将其作为参数传递给Redux.applyMiddleware(),然后,Redux.applyMiddleware()可以作为createStore()函数的第二个可选参数。然后,想要创建异步action,就要在action创建器(action creator)中返回一个函数,该函数将dispatch作为参数。在此函数中,我们可以发送action并执行异步请求。下面的例子通过调用setTimeout()模拟异步请求,通常在发起任何异步行为之前发送一个action,以便应用程序的state知道某些数据正被请求(例如state可能会显示一个加载图标)。然后,一旦我们接收到数据,就可以发送另一个action,此action携带了作为装备的数据以及标志action已完成的信息。记住,要创建异步action,是将dispatch作为参数传递给action创建器,然后将action直接传递给dispatch,剩下的事情中间件会处理。
const REQUESTING_DATA = 'REQUESTING_DATA' const RECEIVED_DATA = 'RECEIVED_DATA' const requestingData = () => { return {type: REQUESTING_DATA} } const receivedData = (data) => { return {type: RECEIVED_DATA, users: data.users} } const handleAsync = () => { return function(dispatch) { //发送请求的action dispatch(requestingData()); //使用setTimeout()模拟API调用 setTimeout(function() { let data = { users: ['Jeff', 'William', 'Alice'] } //发送收到数据的action dispatch(receivedData(data));//这里的data是setTimeout()里的data }, 2500); } }; const defaultState = { fetching: false, users: [] }; const asyncDataReducer = (state = defaultState, action) => { switch(action.type) { case REQUESTING_DATA: return { fetching: true, users: [] } case RECEIVED_DATA: return { fetching: false, users: action.users } default: return state; } }; const store = Redux.createStore( asyncDataReducer, Redux.applyMiddleware(ReduxThunk.default) );//Redux.applyMiddleware()作为Redux.createStore()的第二个参数。
看个例子回顾一下:用Redux实现一个简单的计数器。
// Define a constant for increment action types const INCREMENT = 'INCREMENT'; // Define a constant for decrement action types const DECREMENT = 'DECREMENT'; // Define the counter reducer which will increment or decrement the state based on the action it receives const counterReducer = (state=0,action)=>{ switch(action.type){ case INCREMENT: return state+1 case DECREMENT: return state-1 default: return state; } }; // Define an action creator for incrementing const incAction = ()=>{return{type:INCREMENT}}; // Define an action creator for decrementing const decAction = ()=>{return{type:DECREMENT}}; // Define the Redux store here, passing in your reducers const store = Redux.createStore(counterReducer); //发送一个action给store store.dispatch({type:INCREMENT});
Redux并没有主动在其store或reducer中强制执行state不变性,这一责任落在程序员身上。好在,JavaScript(尤其是ES6)提供了一些有用的工具,无论它是字符串、数字、数组还是对象,使用这些工具都能增强其state的不可变性。注意,字符串和数字是原始值,本质上是不可变的。换句话说,3就是3,你不能改变数字3的值。然而,数组或对象是可变的。在实践中,我们的state可能由一个数组或对象组成,因为这在表示许多类型信息时是一种有用的数据结构。看个例子:
const ADD_TO_DO = 'ADD_TO_DO'; //表示要执行的任务的字符串列表: const todos = [ 'Go to the store', 'Clean the house', 'Cook dinner', 'Learn to code', ]; const immutableReducer = (state = todos, action) => { switch(action.type) { case ADD_TO_DO: {//不能改变state!而.push()和.splice()会改变原有数组,.concat()、.slice()和扩展操作符[…array]不改变原有数组只是返回一个新数组。 return state.concat(action.todo); } default: return state; } }; const addToDo = (todo) => { return { type: ADD_TO_DO, todo } } const store = Redux.createStore(immutableReducer);
ES6提供的一种帮助在Redux中实现状态不变性的解决方案是spread操作符:…,spread运算符有多种应用,其中一种就是从现有数组生成新数组,例如:
let newArray = [...myArray];
newArray现在是myArray的克隆副本。这两个数组仍然各自单独存在于内存中。如果执行“newArray.push(5)”这样语句,改变的只是newArray,myArray不会改变。spread运算符'…'有效地将myArray中的值扩展到一个新数组中。要克隆数组并在新数组中添加其他值,可以写成:
[...myArray, 'new value'],这将返回一个由myArray中的值、字符串new value作为最后一个值组成的新数组。扩展语法可以在这样的数组组合中多次使用,注意,它只会生成数组的浅拷贝,也就是说,它只为一维数组提供不可变的数组操作。看个例子:
const immutableReducer = (state = ['Do not mutate state!'], action) => { switch(action.type) { case 'ADD_TO_DO': //不要改变state return [...state,action.todo]; default: return state; } }; const addToDo = (todo) => { return { type: 'ADD_TO_DO', todo } } const store = Redux.createStore(immutableReducer); //测试: console.log(store.dispatch(addToDo('eat')));//{ type: 'ADD_TO_DO', todo: 'eat' }
假设state是一个数组,如何从state中删除指定项然后返回新state呢?
const immutableReducer = (state = [0,1,2,3,4,5], action) => { switch(action.type) { case 'REMOVE_ITEM': //不要改变state!! //法一: //return [...state].slice(0,action.index).concat([...state].slice(action.index+1,state.length));//slice能复制已存在的数组元素,并以数组的形式返回这些元素。它接受2个参数,第一个参数是开始提取的索引,第二个参数是停止提取的索引(提取将持续到此,但不包括该索引处的元素)。如果未提供参数,则默认值是从数组的开始到结束,即会复制粘贴整个数组。只提供一个参数,则从该参数开始,一直复制到结尾。 //法二: return [...state].filter((item,index)=>{return index!==action.index});//filter方法对数组的每个元素调用函数,最后返回一个新数组,该数组只包含该函数返回true的元素 default: return state; } }; const removeItem = (index) => { return { type: 'REMOVE_ITEM', index } } const store = Redux.createStore(immutableReducer);
当state是一个对象时,有什么方法可以帮助增强state的不可变性呢?一个有用工具是Object.assign()。Object.assign()接受目标对象和来源对象作为参数(来源对象可以有多个),它会将属性从源对象映射到目标对象,任何匹配的属性都会被源对象中的属性覆盖。此行为通常用于通过传递一个空对象作为第一个参数,后跟要复制的对象来创建对象的浅层副本。下面是一个例子:
const newObject = Object.assign({}, obj1, obj2); 创建的newObject新对象包含了obj1和obj2中当前存在的属性。看个例子:
const defaultState = { user: 'CamperBot', status: 'offline', friends: '732,982', community: 'freeCodeCamp' }; const immutableReducer = (state = defaultState, action) => { switch(action.type) { case 'ONLINE': //不要改变state!! return Object.assign({},state,{status:'online'});//对象是键值对的形式,当值是字符时不要忘了引号! default: return state; } }; const wakeUp = () => { return { type: 'ONLINE' } }; const store = Redux.createStore(immutableReducer);
React和Redux结合使用
React 是一个视图库(我们提供数据,然后它以一种高效、可预测的方式呈现视图)。Redux是一个状态管理框架,我们使用它来简化应用程序的state的管理。通常,在React Redux应用程序中,我们创建一个管理整个应用程序state的Redux store,React组件只订阅store中与其角色相关的数据,然后,我们直接从React组件发送action,这会触发store进行更新。
尽管React组件可以管理自己的本地state,但对于一个复杂的应用程序来说,通常最好使用Redux将应用程序state保存在一个位置(当然也有例外:单个组件可以具有仅针对它们的局部state)。最后,由于Redux不是被设计用于与React一起使用的,所以我们需要使用react-redux包,它提供了一种将Redux的state和dispatch作为props传递给React组件的方法。
下面,我们将创建一个简单的React组件,它允许我们输入新的文本消息,每点一次按钮,新文本会被添加到在视图中显示的messages数组末尾中。然后创建一个Redux store和action用来管理文本array的state。最后,使用react-redux将redux store与组件连接起来,从而将本地state提取到redux store中。
class DisplayMessages extends React.Component { constructor(props) { super(props); //记住state初始化是放在constructor方法里的!! this.state = { input: '', messages: [] } this.handleChange=this.handleChange.bind(this); this.submitMessage=this.submitMessage.bind(this); }; handleChange(e){ //不能直接修改state的属性值,要通过setState修改state的属性值 this.setState( { input:e.target.value, messages:this.state.messages//输入框一有更改,就把之前赋有值的messages数组再赋给当前messages数组 } ) }; submitMessage(){ this.setState( { messages:[...this.state.messages,this.state.input],//每点击一次按钮,就把当前输入框的内容添加到messages数组的最后一项 input:'' } ) } render() { return ( <div> <h2>Type in a new Message:</h2> <input type='text' onChange={this.handleChange} value={this.state.input}/>{/*加value是为了让input元素呈现state里的input值*/} <button onClick={this.submitMessage}>Submit</button> <ul> {this.state.messages.map((item)=>{ return <li key={item}>{item}</li> }) } </ul> </div> ); } };
完成了React组件之后,我们需要将它在本地执行的state逻辑提取到Redux中,这是将简单的React应用程序连接到Redux的第一步。上面的应用程序唯一的功能是将用户的新消息添加到无序列表ul中(为了演示React和Redux如何协同工作,这个示例很简单)。下面创建Redux:
const ADD='ADD'; //action创建器(为了添加message) const addMessage=(message)=>{ return { type:ADD, message//在返回的action中包含message } } //这个reducer应该将“message”添加到state中保存的messages数组中,或者返回当前的state。 const messageReducer=(state=[],action)=>{ switch(action.type){ case ADD: return [...state,action.message]; default: return state; } } //Redux store(为了处理message的数组) const store=Redux.createStore(messageReducer);
下一步是为React提供对Redux store的访问,以及它发送更新所需的action。React Redux提供了react-redux包来帮助我们完成这些任务。React Redux提供了一个具有两个关键特性的小型API:Provider和connect。先来看Provider ,它是React Redux的包装组件,用于包装我们的React应用程序,这个包装器允许我们访问整个组件树中的Redux store和dispatch函数。Provider需要两个props:Redux store和应用程序的子组件。为应用程序组件定义“Provider”语法如下:
<Provider store={store}> <App/> </Provider>
使用Provider将上面的Redux连接到上面的React(都写在同一个JS文件里):
//AppWrapper中的render方法中要用到Provider const Provider = ReactRedux.Provider;//React Redux在这里作为全局变量提供 class AppWrapper extends React.Component { //渲染Provider constructor(props){ super(props); } render(){ return (<Provider store={store}><DisplayMessages/></Provider>); } };
完整代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
// Redux: const ADD = 'ADD'; const addMessage = (message) => { return { type: ADD, message } }; const messageReducer = (state = [], action) => { switch (action.type) { case ADD: return [ ...state, action.message ]; default: return state; } }; const store = Redux.createStore(messageReducer); // React: class DisplayMessages extends React.Component { constructor(props) { super(props); this.state = { input: '', messages: [] } this.handleChange = this.handleChange.bind(this); this.submitMessage = this.submitMessage.bind(this); } handleChange(event) { this.setState({ input: event.target.value }); } submitMessage() { this.setState((state) => { const currentMessage = state.input; return { input: '', messages: state.messages.concat(currentMessage) }; }); } render() { return ( <div> <h2>Type in a new Message:</h2> <input value={this.state.input} onChange={this.handleChange}/><br/> <button onClick={this.submitMessage}>Submit</button> <ul> {this.state.messages.map( (message, idx) => { return ( <li key={idx}>{message}</li> ) }) } </ul> </div> ); } }; const Provider = ReactRedux.Provider; class AppWrapper extends React.Component { constructor(props){ super(props); } render(){ return (<Provider store={store}><DisplayMessages/></Provider>); } }; //不用再写下面这句了!!上面内容会通过API呈现到DOM。 //将AppWrapper呈现到DOM //ReactDOM.render(<AppWrapper/>,document.getElementById('challenge-node'));
Provider组件允许我们向React组件提供state和dispatch,但必须准确指定我们想要的state或action,这样才能确保每个组件都只能访问其所需的state。我们可以通过创建两个函数来实现这一点:mapStateToProps() 和mapDispatchToProps()。在这些函数中,我们可以声明想要访问哪些state片段,以及能dispatch哪些action创建器。一旦这些函数就位,我们就能使用React Redux 的connect方法将它们连接到我们的组件。
看下mapStateToProps():
const state = []; /*将数组作为参数state传递给mapStateToProps,返回一个对象,此对象的messages键的值为参数state代表的数组*/ /*法一(虽然可行,但不能这样定义,后面用时不方便。。。): function mapStateToProps(state){ return {messages:state} };*/ //法二: const mapStateToProps=(state)=>{ return {messages:state} } /*在幕后,React Redux使用store.subscribe()方法实现mapStateToProps()*/
mapDispatchToProps()函数用于为React组件提供特定的action创建器,以便组件可以针对Redux store发送action。它的结构类似于mapStateToProps()函数,返回一个将发送的action映射到属性名称的对象,此属性名称将成为组件的props。不同的是,每个属性不是返回一段state,而是返回一个函数,该函数使用action创建器和任何相关的action数据调用dispatch。我们能访问这个dispatch,是因为在定义函数时它就作为参数传递给了mapDispatchToProps() ,就像我们将state传递给mapStateToProps()一样。在幕后,React Redux使用Redux的store.dispatch()方法实现mapDispatchToProps()里的这些dispatch,这与它对映射到state的组件使用store.subscribe()的方式类似。例如,有一个叫 loginUser()的action创建器,它将username作为action的有效载荷,从mapDispatchToProps()为这个action创建器返回的对象如下所示:
{ submitLoginUser: function(username) { dispatch(loginUser(username)); } }
看下mapDispatchToProps():
//action创建器 const addMessage = (message) => { return { type: 'ADD', message: message } }; //编写mapDispatchToProps()函数: const mapDispatchToProps=(dispatch)=>{ return { submitNewMessage:function(newMessage){dispatch(addMessage(newMessage))} };//返回一个对象,此对象有一个submitNewMessage属性,值为dispatch函数,该函数在发送addMessage()时为新message添加一个参数newMessage。 };
现在已经编写了mapStateToProps()和mapDispatchToProps()函数,我们可以使用它们将state和dispatch映射到某一个React组件的props,那么具体怎么操作呢?React Redux中的connect方法可以帮我们完成此步骤,此方法接受两个可选参数:mapStateToProps() 和mapDispatchToProps()。为什么是可选的?因为我们可能有一个只需要访问state但不需要发送任何action的组件,反之亦然。使用此方法时,先将函数作为参数传入,然后立即使用组件调用结果。这个语法有点不寻常,就长下面这样:
connect(mapStateToProps, mapDispatchToProps)(MyComponent)
注意:如果想省略connect方法的一个参数,在其对应位置传个null即可。看个具体例子:
const addMessage = (message) => { return { type: 'ADD', message: message } }; //mapStateToProps函数 const mapStateToProps = (state) => { return { messages: state } }; //mapDispatchToProps函数 const mapDispatchToProps = (dispatch) => { return { submitNewMessage: (message) => { dispatch(addMessage(message)); } } }; //Presentational类组件 class Presentational extends React.Component { constructor(props) { super(props); } render() { return <h3>This is a Presentational Component</h3> } }; //connect方法 const connect = ReactRedux.connect; //使用ReactRedux全局对象中的Connect方法将组件Presentational连接到Redux: const ConnectedComponent=connect(mapStateToProps,mapDispatchToProps)(Presentational); /*最后DOM页面呈现如下: This is a Presentational Component */
既然我们知道了如何使用connect将React连接到Redux,就能将所学应用到例子中处理messages的React组件中。上面的例子中,连接到Redux的组件被命名为Presentational,这可不是随意的,该术语通常指不直接连接到Redux的React组件,他们只是负责UI的呈现(根据收到的props来呈现)。相比之下,连接到Redux的容器组件,通常负责将action发送到store,并通常将store state作为props传递给子组件。看代码:
// Redux: const ADD = 'ADD'; const addMessage = (message) => { return { type: ADD, message: message } }; const messageReducer = (state = [], action) => { switch (action.type) { case ADD: return [ ...state, action.message ]; default: return state; } }; const store = Redux.createStore(messageReducer); // React: class Presentational extends React.Component { constructor(props) { super(props); this.state = { input: '', messages: [] } this.handleChange = this.handleChange.bind(this); this.submitMessage = this.submitMessage.bind(this); } handleChange(event) { this.setState({ input: event.target.value }); } submitMessage() { this.setState((state) => { const currentMessage = state.input; return { input: '', messages: state.messages.concat(currentMessage) }; }); } render() { return ( <div> <h2>Type in a new Message:</h2> <input value={this.state.input} onChange={this.handleChange}/><br/> <button onClick={this.submitMessage}>Submit</button> <ul> {this.state.messages.map( (message, idx) => { return ( <li key={idx}>{message}</li> ) }) } </ul> </div> ); } }; // React-Redux: const mapStateToProps = (state) => { return { messages: state } }; const mapDispatchToProps = (dispatch) => { return { submitNewMessage: (newMessage) => { dispatch(addMessage(newMessage)) } } }; const Provider = ReactRedux.Provider; const connect = ReactRedux.connect; //定义容器组件Container,使用connect方法将Presentational组件连接到Redux: const Container=connect(mapStateToProps,mapDispatchToProps)(Presentational); class AppWrapper extends React.Component { constructor(props) { super(props); } render() { return ( <Provider store={store}>{/*呈现React Redux的Provider组件。将Redux的store作为prop传给Provider。*/} <Container/>{/*将Container作为子组件呈现*/} </Provider> ); } };
我们已经编写了所有的Redux代码,以便Redux可以控制我们的React消息应用程序的state管理。现在Redux已连接,但state还是在Presentational组件的本地处理,我们需要将state管理从Presentational组件提取到Redux中。name如何将本地state提取到Redux?看例子:
// Redux: const ADD = 'ADD'; const addMessage = (message) => { return { type: ADD, message: message } }; const messageReducer = (state = [], action) => { switch (action.type) { case ADD: return [ ...state, action.message ]; default: return state; } }; const store = Redux.createStore(messageReducer); // React: const Provider = ReactRedux.Provider; const connect = ReactRedux.connect; //删除本地state的messages属性,message将由Redux管理 class Presentational extends React.Component { constructor(props) { super(props); this.state = { input: '' //messages: [] } this.handleChange = this.handleChange.bind(this); this.submitMessage = this.submitMessage.bind(this); } handleChange(event) { this.setState({ input: event.target.value }); } //修改submitMessage函数,使其从this.props发送submitNewMessage(),从state传入目前输入的message作为参数(因为从本地state中删除了messages,所以在这里也从对this.setState()的调用中删除messages属性)。 submitMessage() { this.props.submitNewMessage(this.state.input); this.setState({ input: '' //messages: state.messages.concat(state.input) }); } render() { return ( <div> <h2>Type in a new Message:</h2> <input value={this.state.input} onChange={this.handleChange}/><br/> <button onClick={this.submitMessage}>Submit</button> <ul> {/*修改以下呈现的数据,以使其映射呈现从props(而不是从state)接收到的消息 {this.state.messages.map((item, index) => { return ( <li key={index}>{item}</li> ) }) } */} {this.props.messages.map((item, index) => { return ( <li key={index}>{item}</li> ) }) } </ul> </div> ); } }; //映射对象给props(将messages属性传给props) const mapStateToProps = (state) => { return {messages: state} }; //映射发送action的方法给props(将submitNewMessage方法传给props) const mapDispatchToProps = (dispatch) => { return { //注意,submitNewMessage是方法!接收一个message参数。 submitNewMessage: (message) => { dispatch(addMessage(message)) } } }; //使用connect将React连接到Redux //定义子组件Container,Container是连接到Redux的容器组件,通常负责将action发送到store,并通常将store的state作为props传递给子组件 const Container = connect(mapStateToProps, mapDispatchToProps)(Presentational); //Provider是React Redux的包装组件,用于包装我们的React应用程序,这个包装器允许我们访问整个组件树中的Redux store和dispatch函数。Provider需要两个props:Redux store和应用程序的子组件 class AppWrapper extends React.Component { render() { return ( <Provider store={store}> <Container/> </Provider> ); } };
以上为Redux管理state的例子【此示例还说明了与此同时组件还能具有本地状态:组件仍然在其本地state下跟踪用户的input】。
Redux就是这样在React之上提供有用的state管理框架的。最初只使用React的本地state就获得了相同的结果,这通常在简单的应用程序中可行。然而,随着我们的应用程序越来越大、越来越复杂,我们的state管理也越来越复杂。这就是Redux解决的问题。
在我们自己的机器上使用npm和文件系统,语法是什么样子的呢?除了import语句(import语句引入了保证代码能正常运行的所有依赖项),其他代码看起来和上面例子类似。最后,编写React和Redux代码通常需要一些配置,这会变得有些复杂,参考文章https://www.freecodecamp.org/news/install-react-with-create-react-app/ ,文中讲解了如何使用create-react-app安装React.js。
在我们的机器上编写React应用程序的大概语法如下:
import React from 'react' import ReactDOM from 'react-dom' import { Provider, connect } from 'react-redux' import { createStore, combineReducers, applyMiddleware } from 'redux' import thunk from 'redux-thunk' import rootReducer from './redux/reducers' import App from './components/App' const store = createStore( rootReducer, applyMiddleware(thunk) ); ReactDOM.render( <Provider store={store}> <App/> </Provider>, document.getElementById('root') );
。。。