美文网首页
React学习与进阶(更新中)

React学习与进阶(更新中)

作者: nimw | 来源:发表于2018-12-14 19:56 被阅读11次

1. 环境搭建

1.1 脚手架工具

  1. 使用create-react-app创建项目
create-react-app my-react-app
  1. 启动项目
cd my-react-app 
yarn start

注释:也可以使用npm start启动项目。

  1. mock数据

① 创建mock数据文件

// /public/api/data.json
{
  "success": true,
  "data": [1, 2, 3]
}

② 请求数据

fetch('/api/data.json')
  .then(res => res.json())
  .then(data => console.log(data))
 // {data: [1, 2, 3], success: true}

2. 新特性

  1. React 中一个常见模式是为一个组件返回多个元素。Fragments 可以让你聚合一个子元素列表,并且不在DOM中增加额外节点。
class Columns extends React.Component {
  render() {
    return (
      <React.Fragment>
        <td>Hello</td>
        <td>World</td>
      </React.Fragment>
    );
  }
}

注意:① key 是唯一可以传递给 Fragment 的属性。② 在 React 中, <></><React.Fragment><React.Fragment/> 的语法糖。

  1. Strict Mode严格模式
    StrictMode是一个用以标记出应用中潜在问题的工具。就像FragmentStrictMode不会渲染任何真实的UI。它为其后代元素触发额外的检查和警告。
import { StrictMode, Component } from 'react'

class Child extends Component {
  // 以下三个函数在 React v16.3 已不被推荐,未来的版本会废弃。
  componentWillMount() {
    console.log('componentWillMount')
  }
  componentWillUpdate() {
    console.log('componentWillUpdate')
  }
  componentWillReceiveProps() {
    console.log('componentWillReceiveProps')
  }
  render() {
    return (
      <div />
    )
  }
}

export default class StrictModeExample extends Component {
  render() {
    return (
      <StrictMode>
        <Child />
      </StrictMode>
    )
  }
}

由于在StrictMode内使用了三个即将废弃的API,打开控制台 ,可看到如下错误提醒:

控制台报错信息
注释:严格模式检查只在开发模式下运行,不会与生产模式冲突。
  1. createRef (v16.3)
    (1) 老版本ref使用方式
    ① 字符串形式: <input ref="input" />
    ② 回调函数形式:<input ref={input => (this.input = input)} />
    (2) 字符串形式缺点
    ① 需要内部追踪 refthis 取值,会使 React 稍稍变慢。
    ② 有时候 this 与你想象的并不一致。
import React from 'react'

class Children extends React.Component {
  componentDidMount() {
    // <h1></h1>
    console.log('children ref', this.refs.titleRef)
  }
  render() {
    return (
      <div>
        {this.props.renderTitle()}
      </div>
    )
  }
}

class Parent extends React.Component {
  // 放入子组件渲染
  renderTitle = () => (
    <h1 ref='titleRef'>{this.props.title}</h1>
  )

  componentDidMount() {
    // undefined
    console.log('parent ref:', this.refs.titleRef)
  }
  render() {
    return (
      <Children renderTitle={this.renderTitle}></Children>
    )
  }
}

export default Parent

  1. 调用setState更新状态时,若之后的状态依赖于之前的状态,推荐使用传入函数形式。
    语法:setState((prevState, props) => stateChange, [callback])
    例如,假设我们想通过props.step在状态中增加一个值:
this.setState((prevState, props) => {
  return {counter: prevState.counter + props.step};
});
  1. 错误边界
    (1) 错误边界用于捕获其子组件树 JavaScript 异常,记录错误并展示一个回退的 UIReact 组件,而不是整个组件树异常导致页面空白。
    (2) 错误边界在渲染期间、生命周期方法内、以及整个组件树构造函数内捕获错误。
    (3) 组件如果定义了static getDerivedStateFromError()componentDidCatch()中的任意一个或两个生命周期方法 。当其子组件抛出错误时,可使用static getDerivedStateFromError()更新state,可使用componentDidCatch()记录错误信息。
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // Update state so the next render will show the fallback UI.
    return { hasError: true };
  }

  componentDidCatch(error, info) {
    // You can also log the error to an error reporting service
    logErrorToMyService(error, info);
  }

  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children; 
  }
}

而后你可以像一个普通的组件一样使用:

<ErrorBoundary>
  <MyWidget />
</ErrorBoundary>

注释:getDerivedStateFromErrorcomponentDidCatch方法使用一个即可捕获子组件树错误。

  1. Portal
    (1) Portals 提供了一种很好的将子节点渲染到父组件以外的 DOM 节点的方式。
    (2) 通过 Portals 进行事件冒泡
    尽管 portal 可以被放置在DOM 树的任何地方,但在其他方面其行为和普通的 React 子节点行为一致。一个从 portal 内部会触发的事件会一直冒泡至包含 React 树 的祖先。
import React from 'react';
import ReactDOM from 'react-dom';

class Modal extends React.Component {
  render() {
    return this.props.clicks % 2 === 1
      ? this.props.children
      : ReactDOM.createPortal(
        this.props.children,
        document.getElementById('modal'),
      );
  }
}

class Child extends React.Component {
  componentWillUnmount() {
    debugger;
  }

  render() {
    return (
      <button>Click</button>
    );
  }
}

class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {clicks: 0};
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState(prevState => ({
      clicks: prevState.clicks + 1
    }));
  }

  render() {
    return (
      <div onClick={this.handleClick}>
        <p>Number of clicks: {this.state.clicks}</p>
        <Modal clicks={this.state.clicks}>
          <Child />
        </Modal>
      </div>
    );
  }
}

export default Parent

注释:当组件由portal渲染方式切换为普通渲染方式,会导致该组件被卸载之后重新渲染。组件放大功能如果通过portal方式实现,放大前的状态(滚动位置、焦点位置等)无法保持。

3. React生态圈

3.1 使用Charles实现mock数据

  1. 桌面创建todolist.json文件
➜  ~ cd Desktop
➜  Desktop touch todolist.json
  1. 编辑todolist.json文件
    ["Dell", "Lee", "Imooc"]
  2. 打开Charles代理
    Charles - Proxy - macOS Proxy
  3. 配置Charles本地代理
    Charles - Tools - Map Local Settings - add
    ② 添加请求代理映射
    image.png
  4. 使用axios请求mock数据
    ① 安装axois
➜  my-react-app git:(master) ✗ yarn add axios 

注释:也可以使用npm install axios --save安装。
② 使用axios请求数据

import axios from 'axios'
//省略...
axios.get('/api/todolist')
  .then(data => {
    console.log(data)
  })
  .catch(err => {
    console.log(err)
  }) 

3.2 react-transition-group

  1. 文档地址
  2. 安装react-transition-group
➜  my-react-app git:(master) ✗ yarn add react-transition-group
  1. CSSTransition组件的简单使用
import React, { Component } from 'react';
import { CSSTransition } from 'react-transition-group'
import './style.css'

class App extends Component {

  constructor(props, context){
    super(props, context)
    this.state = {
      show: true
    }
  }

  render() {
    
    return (
      <div>
        <CSSTransition
          in={this.state.show}
          timeout={1000}
          classNames='fade'
          appear={true} //第一次展示时也添加动画效果
          unmountOnExit //出场之后将元素移除
          onEntered={   //入场动画结束钩子函数 
            el => console.log(el) //el表示内部元素
          }  
        >
          <div>Hello</div>
        </CSSTransition>
        <button onClick={this._toggleShow}>Toggle</button>
      </div>
    );
  }

  _toggleShow = () => {
    this.setState({
      show: !this.state.show
    })
  }
}

export default App;
/* 定义class为fade的
CSSTransition元素第一次展示时动画 */
.fade-appear {
  opacity: 0;
}

.fade-appear-active {
  opacity: 1;
  transition: opacity 1s ease-in
}

/* 定义class为fade的
CSSTransition元素入场动画 */
.fade-enter {
  opacity: 0;
}

.fade-enter-active {
  opacity: 1;
  transition: opacity 1s ease-in
}

.fade-enter-done {
  opacity: 1;
}

/* 定义class为fade的
CSSTransition元素出场动画 */
.fade-exit {
  opacity: 1;
}

.fade-exit-active {
  opacity: 0;
  transition: opacity 1s ease-in
}

.fade-exit-done {
  opacity: 0;
}
  1. CSSTransition组件的其它钩子函数
    onEnter:入场动画执行第一帧时钩子函数
    onEntering:入场动画执行第二帧时钩子函数
    onEntered:入场动画执行最后一帧钩子函数
    onExit:出场动画执行第一帧时钩子函数
    onExiting:出场动画执行第二帧时钩子函数
    onExited:出场动画执行最后一帧钩子函数
  2. Transition是更底层组件,CSSTransition组件是根据Transition组件实现的。

3.3 Redux

3.3.1 Redux入门

  1. Redux Devtools调试插件安装
    设置 - 扩展程序 - 打开Chrome网上应用店 - Redux Devtools
  2. 使用Redux实现TodoList

(1) 安装redux

➜  my-react-app git:(master) ✗ yarn add redux

(2) 创建Store

// src/store/index.js
import { createStore } from 'redux'
import reducer from './reducer'

//store是唯一的,只有store能改变state
const store = createStore(
  reducer,
  //Redux DevTools调试工具
  window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)

export default store

(3) 创建actionTypes

// src/store/actionTypes.js
export const CHANGE_INPUT_VALUE = 'change_input_value'
export const ADD_TODO_ITEM = 'add_todo_item'
export const DELETE_TODO_ITEM = 'delete_todo_item'

(4) 创建actionCreators

// src/store/actionCreators.js
import { CHANGE_INPUT_VALUE, ADD_TODO_ITEM, DELETE_TODO_ITEM } from './actionTypes'

export const getInputChangeAction = (value) => ({
  type: CHANGE_INPUT_VALUE, value
})

export const getAddTodoItemAction = () => ({
  type: ADD_TODO_ITEM
})

export const getDeleteTodoItemAction = (value) => ({
  type: DELETE_TODO_ITEM, value
})

(5) 实现reducer

// src/store/reducer.js
import { CHANGE_INPUT_VALUE, ADD_TODO_ITEM, DELETE_TODO_ITEM } from './actionTypes'

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

//redux是一个纯函数,输入固定则输出固定,且没有副作用
//redux不应修改旧的state
export default (state = defaultState, action) => {
  switch(action.type){
    case  CHANGE_INPUT_VALUE:
      return {
        ...state,
        inputValue: action.value
      }  
    case ADD_TODO_ITEM:
      return {
        ...state,
        inputValue: '',
        list: [...state.list, state.inputValue]
      }
    case DELETE_TODO_ITEM:
      return {
        ...state,
        list: state.list.filter(
          (item, index) => index !== action.value
        )
      }
  }
  return state
}

(6) 实现TodoList组件

// src/TodoList.js
import React, { Component } from 'react';
import { Input, Button, List } from 'antd'
import store from './store'
import {getInputChangeAction, getAddTodoItemAction, getDeleteTodoItemAction} from './store/actionCreators'

class TodoList extends Component {
  constructor(props, context) {
    super(props, context)
    this.state = store.getState()
    this._handleStoreChange = this._handleStoreChange.bind(this)
    this._handleInputChange = this._handleInputChange.bind(this)
    this._handleButtonClick = this._handleButtonClick.bind(this)
    //订阅Store变化,执行_handleStoreChange方法
    store.subscribe(this._handleStoreChange)
  }

  render() {
    return (
      <div>
        <div style={{display: 'flex'}}>
          <Input 
            placeholder='todo info'
            value = {this.state.inputValue}
            onChange={this._handleInputChange}
          />
          <Button 
            type="primary"
            onClick={this._handleButtonClick}
          >
            提交
          </Button>
        </div>
        <List
          bordered
          dataSource={this.state.list}
          renderItem={(item, index) => (
              <List.Item onClick={this._handleItemDelete.bind(this, index)}>{item}</List.Item>
            )
          }
        />
      </div>
    )
  }

  _handleStoreChange() {
    this.setState(store.getState())
  }

  _handleInputChange(e) {
    store.dispatch(getInputChangeAction(e.target.value))
  }

  _handleButtonClick() {
    store.dispatch(getAddTodoItemAction())
  }

  _handleItemDelete(index) {
    store.dispatch(getDeleteTodoItemAction(index))
  }
}

export default TodoList;
  1. Redux核心API
    (1) createStore: 创建应用唯一的store
    (2) store.dispatch:派发action, 该action会传递给store
    (3) store.getState:获取store中所有数据内容。
    (4) store.subscribe:订阅store改变,绑定回调函数。

3.3.2 Redux中间件

  1. Redux中间件介绍
    ① 中间件指actionstore“中间”。中间件是对dispatch方法的升级。
    ② 流程图
    image.png
  2. redux-thunk中间件

① 安装redux-thunk

 yarn add redux-thunk

② 创建store时引入redux-thunk中间件

//src/store/index.js
import { createStore, applyMiddleware, compose } from 'redux'
import reducer from './reducer'
import thunk from 'redux-thunk'

//Redux DevTools调试工具
const composeEnhancers = typeof window === 'object' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
  ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({})
  : compose;

const enhancer = composeEnhancers(
  applyMiddleware(thunk)
);

const store = createStore(reducer, enhancer)

export default store

③ 定义action

//src/store/actionCreators.js
export const getInitListAction = (data) => ({
  type: INIT_LIST_ACTION,
  data
})

export const getInitList = () => {
  return (dispatch) => {
    axios.get('/list.json').then((res) => {
      dispatch(getInitListAction(res.data))
    })
  }
}

④ 分发action

  //src/components/TodoList/TodoList.js
  componentDidMount() {
    store.dispatch(getInitList());
  }

redux-thunk使用总结
使用redux-thunk后,action可以不是一个对象,而是一个方法。 dispatch方法类型的action,会调用该方法,并传入dispatch实参。
在函数内部可以通过该实参dispatch其他action动作。
总结:redux-thunk中间件将异步获取数据代码抽离到aciton中。

  1. redux-saga中间件

① 安装redux-saga

yarn add redux-saga

② 创建store时引入redux-thunk中间件

//src/store/index.js
import { createStore, applyMiddleware, compose } from 'redux'
import reducer from './reducer'
import createSagaMiddleware from 'redux-saga'
import todoSagas from './sagas'

const sagaMiddleware = createSagaMiddleware()

//Redux DevTools调试工具
const composeEnhancers = typeof window === 'object' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
  ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({})
  : compose;

const enhancer = composeEnhancers(applyMiddleware(sagaMiddleware));


//store是唯一的,只有store能改变state
const store = createStore(reducer, enhancer)

sagaMiddleware.run(todoSagas)

export default store

③ 定义sagas

//src/store/sagas.js
import { takeEvery, put } from 'redux-saga/effects'
import { GET_INIT_LIST } from './actionTypes'
import axios from "axios"
import {getInitListAction} from "./actionCreators"

function *getInitList() {
  try {
    const res = yield axios.get('/list.json')
    const action = getInitListAction(res.data)
    yield put(action)
  } catch (e) {
    console.log('list.json 网络请求失败!')
  }
}

//generator函数
function* mySaga() {
  yield takeEvery(GET_INIT_LIST, getInitList);
}

export default mySaga;

④ 分发action

//src/components/TodoList/TodoList.js
componentDidMount() {
  store.dispatch(getInitList())
}

redux-saga使用总结
使用 redux-saga后,action可以不被reducer处理,而是被saga处理。
saga函数内部可以通过put方法dispatch其它action动作。
总结:redux-saga中间件将异步获取数据代码抽离到saga中。

3.3.3 react-redux介绍

  1. 使用react-redux实现TodoList

(1) 安装react-redux

➜  my-react-app git:(master) ✗ yarn add react-redux

(2) 创建Store

// src/store/index.js
import  { createStore } from 'redux'
import reducer from './reducer'

const store = createStore(reducer)

export default store

(3) 使用Provider组件包裹根组件

//src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux'
import TodoList from './components/TodoList/TodoList'
import store from './store'

ReactDOM.render(
  <Provider store={store}>
    <TodoList/>
  </Provider>,
  document.getElementById('root')
);

注释:store传入Provider组件,使Provider内部组件都有机会获取store中的state
(4) 创建actionTypes

// src/store/actionTypes.js
export const CHANGE_INPUT_VALUE = 'change_input_value'
export const ADD_TODO_ITEM = 'add_todo_item'
export const DELETE_TODO_ITEM = 'delete_todo_item'

(5) 创建actionCreators

// src/store/actionCreators.js
import { CHANGE_INPUT_VALUE, ADD_TODO_ITEM, DELETE_TODO_ITEM } from './actionTypes'

export const getInputChangeAction = (value) => ({
  type: CHANGE_INPUT_VALUE, value
})

export const getAddTodoItemAction = () => ({
  type: ADD_TODO_ITEM
})

export const getDeleteTodoItemAction = (value) => ({
  type: DELETE_TODO_ITEM, value
})

(6) 实现reducer

// src/store/reducer.js
import { CHANGE_INPUT_VALUE, ADD_TODO_ITEM, DELETE_TODO_ITEM } from './actionTypes'

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

//redux是一个纯函数,输入固定则输出固定,且没有副作用
//redux不应修改旧的state
export default (state = defaultState, action) => {
  switch(action.type){
    case  CHANGE_INPUT_VALUE:
      return {
        ...state,
        inputValue: action.value
      }  
    case ADD_TODO_ITEM:
      return {
        ...state,
        inputValue: '',
        list: [...state.list, state.inputValue]
      }
    case DELETE_TODO_ITEM:
      return {
        ...state,
        list: state.list.filter(
          (item, index) => index !== action.value
        )
      }
  }
  return state
}

(7) 实现TodoList组件

// src/TodoList.js
import React from 'react'
import { Input, Button, List } from 'antd'
import { connect } from 'react-redux'
import { getInputChangeAction, getAddTodoItemAction, getDeleteTodoItemAction } from '../../store/actionCreators'

const TodoList = (props) => {
  const {inputValue, handleInputChange, addListItem, list, handleItemDelete} = props
  return (
    <div>
      <di>
        <Input value={inputValue} onChange={handleInputChange}/>
        <Button type="primary" onClick={addListItem}>
          提交
        </Button>
      </di>
      <List bordered dataSource={list} renderItem={(item, index) => (
          <List.Item onClick={() => {handleItemDelete(index)}}>
            {item}
          </List.Item>
        )}
      />
    </div>
  )
}

const mapStateToProps = (state) => {
  return {
    inputValue: state.inputValue,
    list: state.list
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    handleInputChange(e) {
      dispatch(getInputChangeAction(e.target.value))
    },
    handleItemDelete(index) {
      dispatch(getDeleteTodoItemAction(index))
    },
    addListItem() {
      dispatch(getAddTodoItemAction())
    }
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(TodoList);

注释:TodoList为一个UI(纯)组件,connect(mapStateToProps, mapDispatchToProps)(TodoList)为一个容器组件。业务逻辑被抽离到mapStateToPropsmapDispatchToProps方法中。

  1. react-reduxredux用法差异
    Provider组件。
    connect方法。
  2. reduxcombineReducers方法的使用
    ① 创建TodoList对应的reducer文件
// ./src/components/TodoList/store/reducer.js
import { CHANGE_INPUT_VALUE, ADD_TODO_ITEM, DELETE_TODO_ITEM } from '../../../store/actionTypes'

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

//redux是一个纯函数,输入固定则输出固定,且没有副作用
//redux不能修改旧的state
export default (state = defaultState, action) => {
  switch(action.type){
    case  CHANGE_INPUT_VALUE:
      return {
        ...state,
        inputValue: action.value
      }
    case ADD_TODO_ITEM:
      return {
        ...state,
        inputValue: '',
        list: [...state.list, state.inputValue]
      }
    case DELETE_TODO_ITEM:
      return {
        ...state,
        list: state.list.filter(
          (item, index) => index !== action.value
        )
      }
  }
  return state
}

② 使用combineReducers

// src/store/reducer.js
import todoListReducer from '../components/TodoList/store/reducer'
import { combineReducers } from 'redux'

export default combineReducers({
  todoList: todoListReducer
})

③ 修改TodoList组件

// src/components/TodoList/TodoList.js
//......
const mapStateToProps = (state) => {
  return {
    inputValue: state.todoList.inputValue,
    list: state.todoList.list
  }
}
//......

3.4 styled-components介绍

  1. js文件中引入css文件的弊端
    引入的css文件在整个html中生效。
  2. 安装styled-components
➜  my-react-app git:(master) ✗ yarn add styled-components
  1. 全局样式

① 创建全局样式组件

//src/style.js
import { createGlobalStyle } from 'styled-components'

export const Globalstyle = createGlobalStyle`
  body {
    margin: 0;
    padding: 0;
  }
`

② 使用全局样式组件

import { Globalstyle } from './style.js'

ReactDOM.render(
  <Provider store={store}>
    <TodoList/>
    <Globalstyle />
  </Provider>,
  document.getElementById('root')
);
  1. 创建带样式的元素

① 创建组件

//src/components/TodoList/style.js
import styled from 'styled-components'
import icon from '../../statics/weixin.jpg'

export const HeaderWrapper  = styled.div.attrs({
  //属性
})`
  height: 56px;
  background: red;
  background: url(${icon});
  &.right {
    float: right
  }
`

注释:&.right表示classrightHeaderWrapper组件的样式。
② 使用组件

import { HeaderWraper } from './style'
const TodoList = (props) => {
  return (
    <HeaderWraper>
     //...
    </HeaderWraper>
  )
}

注释:该组件是一个有样式的div元素包裹层。

3.5 Iconfont阿里云图标

  1. 进入阿里云图标网站
  2. 创建项目并添加图标
  3. 下载Unicode至本地并解压
  4. 拷贝文件
    iconfont.cssiconfont.eoticonfont.svgiconfont.ttficonfont.woff文件拷贝到项目中。项目路径如下:
    ./src/statics/iconfonts/iconfont.XXX
  5. 使用styled-components导入CSS文件
    ① 重命名iconfont.cssiconfont.js
    ② 编辑iconfont.js文件
import { createGlobalStyle } from 'styled-components'

export const IconFontGlobalComponent = createGlobalStyle`
@font-face {font-family: "iconfont";
  src: url('./iconfont.eot?t=1550907977882'); /* IE9 */
  src: url('./iconfont.eot?t=1550907977882#iefix') format('embedded-opentype'), /* IE6-IE8 */
  url('data:application/x-font-woff2;charset=utf-8;base64,dfA2nmlc/BurCg==') format('woff2'),
  url('./iconfont.woff?t=1550907977882') format('woff'),
  url('./iconfont.ttf?t=1550907977882') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */
  url('./iconfont.svg?t=1550907977882#iconfont') format('svg'); /* iOS 4.1- */
}

.iconfont {
  font-family: "iconfont" !important;
  font-size: 16px;
  font-style: normal;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

.iconstar_blue:before {
  content: "\e625";
}
`
  1. 引入全局组件
import { IconFontGlobalComponent } from './statics/iconfonts/iconfont'

ReactDOM.render(
  <Provider store={store}>
    <TodoList/>
    <IconFontGlobalComponent />
  </Provider>,
  document.getElementById('root')
);
  1. 使用字体图标
 <span className="iconfont">&#xe625;</span>

3.6 Immutable的用法

3.6.1 immutable使用

  1. 安装immutable.js
➜  my-react-app git:(master) ✗ yarn add immutable
  1. 改写reducer.js
// ./src/components/TodoList/store/reducer.js
import { CHANGE_INPUT_VALUE, ADD_TODO_ITEM, DELETE_TODO_ITEM } from '../../../store/actionTypes'
import { fromJS } from 'immutable'


const defaultState = fromJS({
  inputValue: '',
  list: []
})

export default (state = defaultState, action) => {
  switch(action.type){
    case  CHANGE_INPUT_VALUE:
      return state
        .set('inputValue', action.value)
    case ADD_TODO_ITEM:
      return state
        .set('list', state.get('list').push(state.get('inputValue')))
        .set('inputValue', '')
    case DELETE_TODO_ITEM:
      return state
        .set('list', state.get('list').filter((item, index) => index !== action.value))
  }
  return state
}

注意:stateimmutable对象,其内部list值也为immutable对象。为保持数据类型统一,当action类型为ADD_TODO_ITEMDELETE_TODO_ITEM时,需要设置list值仍为immutable对象。

  1. 改写TodoList组件
// src/components/TodoList/TodoList.js
//......
const mapStateToProps = (state) => {
  return {
    inputValue: state.todoList.get('inputValue'),
    list: state.todoList.get('list')
  }
}
//......

3.6.2 redux-immutable使用

  1. 安装redux-immutable
➜  my-react-app git:(master) ✗ yarn add redux-immutable
  1. 改写reducer
//src/store/reducer.js
import todoListReducer from '../components/TodoList/store/reducer'
import { combineReducers } from 'redux-immutable'

export default combineReducers({
  todoList: todoListReducer
})

注释:combineReducersredux-immutable中引入,使该函数生成immutable对象。

  1. 改写TodoList组件
// src/components/TodoList/TodoList.js
//......
const mapStateToProps = (state) => {
  return {
    inputValue: state.getIn(['todoList', 'inputValue']),
    list: state.getIn(['todoList','list'])
  }
}
//......

参考资料

相关文章

网友评论

      本文标题:React学习与进阶(更新中)

      本文链接:https://www.haomeiwen.com/subject/orknhqtx.html