1. 环境搭建
1.1 脚手架工具
- 使用
create-react-app
创建项目
create-react-app my-react-app
- 启动项目
cd my-react-app
yarn start
注释:也可以使用npm start
启动项目。
-
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. 新特性
-
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/>
的语法糖。
-
Strict Mode
严格模式
StrictMode
是一个用以标记出应用中潜在问题的工具。就像Fragment
,StrictMode
不会渲染任何真实的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,打开控制台 ,可看到如下错误提醒:
注释:严格模式检查只在开发模式下运行,不会与生产模式冲突。
-
createRef (v16.3)
(1) 老版本ref
使用方式
① 字符串形式:<input ref="input" />
② 回调函数形式:<input ref={input => (this.input = input)} />
(2) 字符串形式缺点
① 需要内部追踪ref
的this
取值,会使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
- 调用
setState
更新状态时,若之后的状态依赖于之前的状态,推荐使用传入函数形式。
语法:setState((prevState, props) => stateChange, [callback])
例如,假设我们想通过props.step
在状态中增加一个值:
this.setState((prevState, props) => {
return {counter: prevState.counter + props.step};
});
- 错误边界
(1) 错误边界用于捕获其子组件树JavaScript
异常,记录错误并展示一个回退的UI
的React
组件,而不是整个组件树异常导致页面空白。
(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>
注释:getDerivedStateFromError
和componentDidCatch
方法使用一个即可捕获子组件树错误。
-
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数据
- 桌面创建
todolist.json
文件
➜ ~ cd Desktop
➜ Desktop touch todolist.json
- 编辑
todolist.json
文件
["Dell", "Lee", "Imooc"]
- 打开
Charles
代理
Charles
-Proxy
-macOS Proxy
- 配置
Charles
本地代理
①Charles
-Tools
-Map Local Settings
-add
② 添加请求代理映射
image.png - 使用
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
- 文档地址
- 安装
react-transition-group
➜ my-react-app git:(master) ✗ yarn add react-transition-group
-
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;
}
-
CSSTransition
组件的其它钩子函数
onEnter
:入场动画执行第一帧时钩子函数
onEntering
:入场动画执行第二帧时钩子函数
onEntered
:入场动画执行最后一帧钩子函数
onExit
:出场动画执行第一帧时钩子函数
onExiting
:出场动画执行第二帧时钩子函数
onExited
:出场动画执行最后一帧钩子函数 -
Transition
是更底层组件,CSSTransition
组件是根据Transition
组件实现的。
3.3 Redux
3.3.1 Redux入门
-
Redux Devtools
调试插件安装
设置 - 扩展程序 - 打开Chrome
网上应用店 -Redux Devtools
- 使用
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;
-
Redux
核心API
(1)createStore
: 创建应用唯一的store
。
(2)store.dispatch
:派发action
, 该action
会传递给store
。
(3)store.getState
:获取store
中所有数据内容。
(4)store.subscribe
:订阅store
改变,绑定回调函数。
3.3.2 Redux中间件
-
Redux
中间件介绍
① 中间件指action
和store
“中间”。中间件是对dispatch
方法的升级。
② 流程图
image.png -
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
中。
-
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介绍
- 使用
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)
为一个容器组件。业务逻辑被抽离到mapStateToProps
和mapDispatchToProps
方法中。
-
react-redux
与redux
用法差异
①Provider
组件。
②connect
方法。 -
redux
中combineReducers
方法的使用
① 创建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
介绍
-
js
文件中引入css
文件的弊端
引入的css
文件在整个html
中生效。 - 安装
styled-components
➜ my-react-app git:(master) ✗ yarn add styled-components
- 全局样式
① 创建全局样式组件
//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')
);
- 创建带样式的元素
① 创建组件
//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
表示class
为right
的HeaderWrapper
组件的样式。
② 使用组件
import { HeaderWraper } from './style'
const TodoList = (props) => {
return (
<HeaderWraper>
//...
</HeaderWraper>
)
}
注释:该组件是一个有样式的div
元素包裹层。
3.5 Iconfont阿里云图标
- 进入阿里云图标网站
- 创建项目并添加图标
- 下载
Unicode
至本地并解压 - 拷贝文件
将iconfont.css
、iconfont.eot
、iconfont.svg
、iconfont.ttf
、iconfont.woff
文件拷贝到项目中。项目路径如下:
./src/statics/iconfonts/iconfont.XXX
- 使用
styled-components
导入CSS
文件
① 重命名iconfont.css
为iconfont.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";
}
`
- 引入全局组件
import { IconFontGlobalComponent } from './statics/iconfonts/iconfont'
ReactDOM.render(
<Provider store={store}>
<TodoList/>
<IconFontGlobalComponent />
</Provider>,
document.getElementById('root')
);
- 使用字体图标
<span className="iconfont"></span>
3.6 Immutable的用法
3.6.1 immutable使用
- 安装
immutable.js
➜ my-react-app git:(master) ✗ yarn add immutable
- 改写
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
}
注意:state
为immutable
对象,其内部list
值也为immutable
对象。为保持数据类型统一,当action
类型为ADD_TODO_ITEM
或DELETE_TODO_ITEM
时,需要设置list
值仍为immutable
对象。
- 改写
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使用
- 安装
redux-immutable
➜ my-react-app git:(master) ✗ yarn add redux-immutable
- 改写
reducer
//src/store/reducer.js
import todoListReducer from '../components/TodoList/store/reducer'
import { combineReducers } from 'redux-immutable'
export default combineReducers({
todoList: todoListReducer
})
注释:combineReducers
从redux-immutable
中引入,使该函数生成immutable
对象。
- 改写
TodoList
组件
// src/components/TodoList/TodoList.js
//......
const mapStateToProps = (state) => {
return {
inputValue: state.getIn(['todoList', 'inputValue']),
list: state.getIn(['todoList','list'])
}
}
//......
网友评论