React简书开发实战课程笔记——3

版权声明:本文为博主原创文章,转载请注明原文出处。 https://blog.csdn.net/qq_41139830/article/details/82814501

笔记继续…


一、在容器组件中拆分出UI组件

上一篇中说到的Todolist.js就是容器组件,它的render方法中返回了很多的UI组件,所以有必要把他们拆分一下

src根目录下新建一个TodolistUI.js文件,将所有的UI组件放在这里面

拆分后,UI组件里面的this.state都无法获取到了,这时就需要父组件(即容器组件)将this.state方法作为参数,传递给UI组件

Todolist.js:

render() {
  return (
    //将this.state、方法都作为参数传递给UI组件
    <TodolistUI
      inputValue={this.state.inputValue}
      list={this.state.list}
      handleInputChange={this.handleInputChange}
      handleBtnClick={this.handleBtnClick}
      handleDeleteClick={this.handleDeleteClick}
    />
  );
}

TodolistUI.js:

import React, { Component, Fragment } from 'react';
import { Input, Button, List } from 'antd';

class TodolistUI extends Component {
  render() {
    return (
      <Fragment>
        <Input
          placeholder='placeholder'
          value={this.props.inputValue}
          onChange={this.props.handleInputChange}
        />
        <Button
          type="primary"
          onClick={this.props.handleBtnClick}
        >提交</Button>

        <List
          header={<div>Header</div>}
          footer={<div>Footer</div>}
          bordered
          dataSource={this.props.list}
          renderItem={(item, index) => (
            <List.Item
              onClick={() => {
                this.props.handleDeleteClick(index)
              }}
            >
              {item}
            </List.Item>
          )}
        />
      </Fragment>
    );
  }
}

export default TodolistUI;

UI组件中,需要将原先所有的this.state换成this.props,将原先的this.方法名换成this.props.方法名

注意:如果此时仍需要向方法传递一个参数,比如:index,就不能直接写成这种形式:onClick={this.props.handleDeleteClick(index)},要改写成这种形式:

onClick={() => {
  this.props.handleDeleteClick(index)
}}

二、无状态组件改写成函数
  • 无状态组件从字面来理解,就是没有state的组件,这样的组件只有一个render方法。
    实际使用的时候最好改写成一个函数,将renderreturn改写成一个函数的return
    这样改写的好处就是提高性能,毕竟创建一个函数比创建一个类性能上要有所提高。

假设有一个无状态组件

class Todolist extends React.Component {
  render() {
    return (
      //..
    );
  }
}

那么就可以将其改写成:

const Todolist = (props) => {
  return (
    //..
  );
}

改写后的函数接收一个参数props,这样原来组件里用到的this.props换成props就行了


三、redux发送异步请求获取数据

我的charles用不了,所以我还是用在线接口数据模拟吧: Mock your HTTP responses to test your REST API

第一篇笔记的最后说过:componentDidMount这个钩子函数的一个用途就是:把只需要发送一次的ajax请求的代码写在里面

所以redux发送异步请求获取数据的代码放在componentDidMount函数里:

Todolist.js:

import { getInitialListItemAction } from './store/actionCreators';

componentDidMount() {
  //下面这个地址就是在线模拟的一个接口的地址
  axios.get('http://www.mocky.io/v2/5ba773293200006900e2e95e/text/json')
    .then((res) => {
      const data = res.data;  //获取模拟的数据
      
      //将数据传给getInitialListItemAction方法,来生成相应的action
      //这个方法的实现在其他文件里
      const action = getInitialListItemAction(data);
      store.dispatch(action);
    })
    .catch((error) => {
      alert(error);
    })
}

先定义一下action的type:

actionTypes.js:

export const INITIAL_LIST_ITEM = 'initial_list_item';

再定义getInitialListItemAction 方法:

actionCreator.js:

import { INITIAL_LIST_ITEM } from './actionTypes';

export const getInitialListItemAction = (data) => ({
  type: INITIAL_LIST_ITEM,
  data
})

这样上面Todolist.js里的:const action = getInitialListItemAction(data); 就能获取到action了。
然后再使用:store.dispatch(action);action传递给storereducer

最后reducer根据actionstore中的数据进行更新:
reducer.js:

import { INITIAL_LIST_ITEM } from './actionTypes';

const defaultState = {
  inputValue: '',
  list: []
};

export default (state = default, action) => {
  if (action.type === INITIAL_LIST_ITEM) {
    const newState = JSON.parse(JSON.stringify(state));
    newState.list = action.data;  //将store中list的值更新为获取到的数据
	
    return newState;
  }

  return state;
}

之后页面重新渲染。


四、redux-thunk处理异步请求

如果将redux发送异步请求获取数据的代码都放在componentDidMount钩子函数中,随着业务逻辑的增加,这个钩子函数可能会变的越来越臃肿。
所以可以利用redux-thunkredux发送异步请求获取数据的代码放在其他函数中。

不使用redux-thunk时,action只能是一个对象,有了redux-thunk之后,action就可以是一个函数了。

一些必须的配置,index.js(src目录下的):

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';

const store = createStore(
  reducer,
  applyMiddleware(thunk)
);

由于已经使用了redux-devtools这个中间件,所以想要同时使用两个中间件,要使用下面的方法:

redux-devtools的github上有文档说明:redux-devtools-extension

import { createStore, applyMiddleware, compose } from 'redux';
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
);

之后就可以让action是一个函数了。

如果不使用redux-thunk,并且让action是一个函数的话,就会报以下错误:
Error: Actions must be plain objects. Use custom middleware for async actions.
就是说:action必须是一个对象


下面是将redux发送异步请求获取数据的代码放在其他函数中的具体实现:

actionCreators.js:

import axios from 'axios';

export const getListItem = () => {
  return (dispatch) => {  //store的dispatch方法可以作为一个参数被接收
    axios.get('http://www.mocky.io/v2/5ba773293200006900e2e95e/text/json')
      .then((res) => {
        const data = res.data;
        const action = getInitialListItemAction(data);
        dispatch(action);
      })
      .catch((error) => {
        alert(error);
      })
  };
} 

Todolist.js:

import getListItem  from './store/actionCreators';

componentDidMount() {
  const action = getListItem();
  store.dispatch(action);  //当dispatch接收到一个函数参数时,会自动执行这个函数
}

代码原理很简单,就是把异步请求数据的代码放在其它函数中,然后在componentDidMount中用dispatch方法执行这个函数即可。


五、redux-saga处理异步请求

redux-thunk一样的是,redux-saga都可用于处理异步请求的代码。redux-thunk的原理是通过对dispatch方法进行升级,使其能接受一个函数作为参数(即action可以是一个函数了),然后将异步请求的代码写在一个非钩子函数中。而redux-saga的原理就有点绕了:

一些必须的配置,index.js(src目录下的):

import { createStore, applyMiddleware, compose } from 'redux';
import reducer from './reducer';
import createSagaMiddleware from 'redux-saga';
import mySaga from './saga';  //saga具体的实现代码都单独放在一个文件中

const sagaMiddleware = createSagaMiddleware();  //生成saga中间件
const composeEnhancers =                        //redux-devtools中间件
  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
  
const enhancer = composeEnhancers(applyMiddleware(sagaMiddleware)); //同时使用两个中间件
const store = createStore( reducer, enhancer );                     //使用reducer和中间件创建store
sagaMiddleware.run(mySaga);                                         //运行mySaga函数(这个函数定义在saga.js文件中)

export default store;

saga具体的实现代码都单独放在一个文件中,所以在store文件夹下新建一个saga.js文件

saga.js:

import { takeEvery } from 'redux-saga/effects';  //takeEvery是saga中的一个方法

function* getInitListItem() {
}

function* mySaga() {
  //takeEvery方法:每当接收到INIT_LIST_TYPE参数,都会自动执行getInitListItem函数
  //每当组件向store传递action时,saga 和 reducer都会默认接收到这个action
  //所以让组件向store传递action,takeEvery就会自动执行getInitListItem函数
  //然后将异步请求的代码放在getInitListItem函数中就能实现获取数据
  yield takeEvery(INIT_LIST_TYPE, getInitListItem);
}

export default mySaga;

saga.js文件里的大体框架如上,其中的函数都必须是ES6generator函数

上面的getInitListItem函数具体实现如下:

import { put } from 'redux-saga/effects';
import { getInitListItemAction } from './actionCreators';

function* getInitListItem() {
  const res = yield axios.get('http://www.mocky.io/v2/5ba773293200006900e2e95e/text/json');
  const action = getInitListItemAction(res.data);
  yield put(action);  //put是saga中的一个方法
}

getInitListItemAction是生成action的方法,它的定义如下:

actionCreators.js:

import { INITIAL_LIST_ITEM } from './actionTypes';

export const getInitListItemAction = (data) => ({
  type: INITIAL_LIST_ITEM,
  data
})

INITIAL_LIST_ITEM被定义在actionTypes.js中:

actionTypes.js:

export const INITIAL_LIST_ITEM= 'initial_list_item';

getInitListItem函数通过put方法store发送action,默认地reducer也接收到action
通过reducer更新store的代码如下:

import { INITIAL_LIST_ITEM } from './actionTypes';

export default (state = defaultState, action) => {
  if (action.type === INITIAL_LIST_ITEM) {
    const newState = JSON.parse(JSON.stringify(state));
    newState.list = action.data;

    return newState;
  }
}

saga.js中完整的代码如下:

import { takeEvery, put } from 'redux-saga/effects';
import { INIT_LIST_TYPE } from './actionTypes';
import { getInitListItemAction } from './actionCreators';
import axios from 'axios';

function* getInitListItem() {
  const res = yield axios.get('http://www.mocky.io/v2/5ba773293200006900e2e95e/text/json');
  const action = getInitListItemAction(res.data);
  yield put(action);
}

function* mySaga() {
  yield takeEvery(INIT_LIST_TYPE, getInitListItem);
}

export default mySaga;

做到这还不行,saga.js的代码写完了,还要组件给saga发送action才行:

Todolist.js:

import { getInitListType } from './store/actionCreators';

componentDidMount() {
  const action = getInitListType();
  store.dispatch(action);
}

由于这个action只起到传递信息的作用就行了,所以只需要getInitListType返回actiontype

actionCreators.js:

import { INIT_LIST_TYPE } from './actionTypes';

export const getInitListType = () => ({
  type: INIT_LIST_TYPE
});

INIT_LIST_TYPE定义在:
actionTypes.js:

export const INIT_LIST_TYPE = 'init_list_type';

这样componentDidMount函数执行时,会向store传递一个actionsaga也会接收到这个actionsaga接收到这个action后通过takeEvery(INIT_LIST_TYPE,getInitListItem);这段代码,自动执行getInitListItem函数getInitListItem函数中写有获取数据并更新store的代码,所以,就实现了通过saga进行异步获取数据的ajax请求


到底用redux-thunk还是redux-saga来异步请求数据,还要视情况而定。


六、redux中间件

在这里插入图片描述

上面一直说的中间件指的是:redux中action和store之间的中间件。


七、react-redux

顾名思义,react-redux就是用来将reactredux连接起来。

  • 安装:npm install react-redux --save
  • 使用:

index.js(src根目录下的):

//import React from 'react';
//import ReactDOM from 'react-dom';
//import Todolist from './Todolist';
import { Provider } from 'react-redux';

const App = (
  <Provider store={store}>  //包裹在Provider中的容器组件,都将接收到store
                            //这样就将react和redux连接了起来
                            
    <Todolist />            //一个容器组件  //可以有多个容器组件,并且所有组件都将接收到store
  </Provider>
);

ReactDOM.render(App, document.getElementById('root'));

容器组件中的代码:

Todolist.js:

//import React, { Component, Fragment } from 'react';
import { connect } from 'react-redux';

class Todolist extends Component {
  render() {
    //...
  }
}

const mapStateToProps = (state) => {
  return {
    //这里写state数据,比如:
    inputValue: state.inputValue
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    //这里写需要将action dispatch到store的方法,例如:
    handleClickBtn(e) {
      const action = {
        type: 'change_input_value',
        value: e.target.value
      }
      dispatch(action);
    }
  }
}

//通过react-redux中的connect方法,将组件Todolist中的state,dispatch映射到props
//mapStateToProps和mapDispatchToProps分别实现映射
export default connect(mapStateToProps, mapDispatchToProps)(Todolist);

映射完之后,Todolist组件中所有的:this.statethis.方法名,都直接替换成:this.propsthis.props.方法名


有错误或不足,欢迎评论指正~

待续…

猜你喜欢

转载自blog.csdn.net/qq_41139830/article/details/82814501
今日推荐