简书Header组件开发(React-Redux管理数据、combineReducers拆分数据)

使用React-Redux进行应用数据的管理

目前我们只在state中存放了一个数据focused,但一旦数据多起来考虑到react传递数据的特点,我们就必须借助React-Redux来帮助管理数据。

项目中安装React-Redux

redux是数据框架,而react-redux是第三方模块,他可以让我们在react中更方便的使用redux

1
2
3
//首先安装redux,再安装react-redux
yarn add redux
yarn add react-redux

复习redux的工作流程:

redux工作流程

  • 首先我们需要有一个store,然后页面从store中取数据
  • 如果页面想改变store中的数据,他需要:
    1. 派发一个action给store
    2. store将之前的数据和action一起给reducer
    3. reducer结合action和之前的数据返回一个新的state给store
    4. store拿到新的数据后替换自身的state,告诉页面自己更新了
    5. 页面的state也更新,页面重新渲染

使用redux和react-redux

  1. 创建storesrc下创建文件夹store,创建index.js文件,在该文件中创建store实例
    • 注意:在创建store时就要将reducer传给store,否则store不会处理数据。
  2. 创建reducer:在文件夹store中创建reducer.js文件用于放置reducer。
    • reducer.js中导出的是一个纯函数(给定固定输入就有固定输出,且不能改变参数值。涉及异步/时间的就不是纯函数)
    • 导出的函数接受两个参数,参数1state其实就是store的state,参数2是action.
    • 导出的函数返回一个state(默认值)/newState(修改后的state)给store。(注意newState是深拷贝出来的)
  3. 在项目的根组件(入口组件)App.js中,引入store,并使用react-redux中的Provider组件,使所有组件都有能力连接store。
  4. Header组件中,使用react-redux的connect方法连接store,原本我们导出的是Header组件,现在改成导出“使用connect(mapStateToProps,mapDispatchToProps)()生成的组件”。
  5. reducer.js中,将Header组件的state中数据放到reducer的defaultState中作为store的默认数据
  6. 在Header组件中想要使用this.props.数据名从store中获取原本放在state中的数据,此时需要先使用connect方法的参数1mapStateToProps()进行store中的数据与props的映射(将store中获取到的数据绑在props上),才能使用this.props.数据名拿到store中的数据。
    • mapStateToProps()接受的参数state是store的state。
  7. 处理事件绑定函数,原本两个绑定函数中都是处理state中数据的,现在我们需要将这两个函数挂在props上(即放在connect的参数2mapDispatchToProps()内定义),新建action并通过dispatch将action传给store,store将action和state传给reducer,在reducer中添加判断,通过action的type决定给store的state返回值

创建store

src-store-index.js创建store:

1
2
3
4
5
6
import { createStore } from "redux";
import reducer from "./reducer";

const store = createStore(reducer);

export default store;

创建reducer

2.src-store-index.js创建reducer:

1
2
3
4
5
const defaultState = {};

export default (state = defaultState, action) => {
return state;
}

引入store,使用Provider组件

App.js中 引入store,并使用react-redux中的Provider组件使所有组件都有能力连接store:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React, { Component } from 'react';
import { Provider } from "react-redux";
import Header from "./common/header";
import store from "./store";
import { Globalstyle } from './style';
import { GlobalIconFontStyle } from "./statics/iconfont/iconfont";

class App extends Component {
render() {
return (
<Provider store={store}>
<Globalstyle />
<GlobalIconFontStyle />
<Header />
</Provider>
);
}
}

export default App;

注意:一般我们将组件的引用放在前面,样式的引用放在后面,

使用connect方法连接store

common-header-index.js中,使用react-redux的connect方法连接store,原本我们导出的是Header组件,现在改成导出“使用connect()生成的组件”:

1
import { connect } from "react-redux";
1
2
3
4
5
6
7
8
9
10
11
12
13
const mapStateToProps = (state) => {
return {

}
}

const mapDispatchProps = (dispatch) => {
return {

}
}

export default connect(mapStateToProps, mapDispatchProps)(Header);

观察页面正常就可以继续写。

将数据放入store中(reducer)

reducer.js中,将Header组件的state中数据放到reducer的defaultState中作为store的默认数据

1
2
3
const defaultState = {
focused: false
};

组件从store中取值

  • 使用connect方法的参数1mapStateToProps()进行store中的数据与props的映射,才能使用props拿到store中的数据。(mapStateToProps()接受的参数state是store的state)
  • 在Header组件中使用props从store中获取原本放在state中的数据focused

common-header-index.js:
先修改mapStateToProps(),将focused绑在props上:

1
2
3
4
5
const mapStateToProps = (state) => {
return {
focused: state.focused
}
}

再将this.state.focused改成this.props.focused即可获取store中的数据focused

此时绑定函数还没迁移,所以在页面上我们只能通过手动改true和false来看效果。

处理事件绑定函数

  • 原本两个绑定函数中都是处理state中数据的。
  • 现在我们需要:
    1. 将这两个函数挂在props上(即放在connect的参数2mapDispatchToProps()内定义)
    2. 新建action并通过dispatch将action传给store,store将action和state传给reducer
    3. 在reducer中添加判断,通过action的type决定给store的state返回值
  • 注意:两个函数中间使用逗号分隔

common-header-index.js:
将这两个函数挂在props上(即放在connect的参数2mapDispatchToProps()内定义),新建action并通过dispatch将action传给store,store将action和state传给reducer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const mapDispatchProps = (dispatch) => {
return {
handleInputFocus() {
const action = {
type: "search_focus"
};
dispatch(action);
},
handleInputBlur() {
const action = {
type: "search_blur"
};
dispatch(action);
}
}
}

调用时从props上获取:

1
2
3
4
5
<NavSearch
className={this.props.focused ? "focused" : ""}
onFocus={this.props.handleInputFocus}
onBlur={this.props.handleInputBlur}
/>

store-reducer.js:
在reducer中添加判断,通过action的type决定给store的state返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const defaultState = {
focused: false
};

export default (state = defaultState, action) => {
if (action.type === "search_focus") {
return {
focused: true
}
}
if (action.type === "search_blur") {
return {
focused: false
}
}
return state;
}

页面动画效果也实现好了。


处理无状态组件

  • 此时Header组件只有render函数,所以可成为无状态组件,为提升性能,我们可以将其转换为无状态组件。
  • 转换步骤
    1. 直接让Header组件等于一个箭头函数const Header = (props) => {}
    2. 原本render函数中的内容复制到箭头函数中return出去(不返回则外部无法获取,因为调用Header组件相当于调用它返回的东西)
    3. 注意该函数接收父级的props作为参数,所以所有this.props.xxx都改成props.xxx

header-index.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
const Header = (props) => {
return (
<HeaderWrapper>
<Logo />
<Nav>
<NavItem className="left active">首页</NavItem>
<NavItem className="left">下载App</NavItem>
<NavItem className="right">登录</NavItem>
<NavItem className="right">
<span className="iconfont">&#xe636;</span>
</NavItem>
<SearchWrapper>
<CSSTransition
in={props.focused}
timeout={200}
classNames="slide"
>
<NavSearch
className={props.focused ? "focused" : ""}
onFocus={props.handleInputFocus}
onBlur={props.handleInputBlur}
/>
</CSSTransition>
<span className={props.focused ? "focused iconfont" : "iconfont"}>
&#xe62d;
</span>
</SearchWrapper>
</Nav>
<Addition>
<Button className="writting">
<span className="iconfont">&#xe6e5;</span>
写文章
</Button>
<Button className="reg">注册</Button>
</Addition>
</HeaderWrapper>
)
}

使用combineReducers对数据拆分管理

  • 虽然我们已经使用redux和react-redux进行数据管理,但页面上的开发者工具Redux DevTools还是灰色的,也就是说F12中的redux测试是不好用的
    Redux DevTools还是灰色
  • 想要使用Redux DevTools就需要在项目的store-index.js中进行配置

配置开发者工具REDUX_DEVTOOLS_EXTENSION

github中搜索redux-devtools-extension,在文档中查看他的高级用法Advanced store setup
高级用法

配置成功:(展示 REDUX_DEVTOOLS_EXTENSION 的具体用法)
配置成功的效果


为什么使用combineReducers拆分数据

  • 如果一个reducer中存放过多数据,就会导致后期代码维护十分困难。(一般一个文件超过300行代码就是没设计好)
  • 所以我们可以将一个reducer拆分成很多个小的reducer,最终再整合起来。
  • redux提供了一个combineReducers模块用于将小的reducer们整合成大的一个reducer

combineReducers使用步骤

  1. header文件夹下=>新建store文件夹=>新建reducer.js文件,用于存储和Header组件相关的store数据/操作
  2. 在src-store-reducer.js中,将分开的小reducer通过 combineReducers 结合成完整的一个reducer。(键名是自定义的,用于在组件中获取store中的数据)
  3. 由于原本放在reducer中的数据迁移到了小reducer中,所以**在组件中规定 store映射到props 的函数(connect方法的参数2mapStateToProps())中的state.focused也应当改为state.header.focused**,组件才能获取到store中的数据。
  4. 优化:在 总reducer 中引入 分reducer 的路径太长,此时可以在 分reducer 的文件夹中新建index.js用于 “引入、输出 分reducer”,这样在 总reducer 中引入 分reducer 时就可以直接从这个index中引入了。

1.header文件夹下=>新建store文件夹=>新建reducer.js文件,用于存储和Header组件相关的store数据(即原本store中reducer的内容):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const defaultState = {
focused: false
};

export default (state = defaultState, action) => {
if (action.type === "search_focus") {
return {
focused: true
}
}
if (action.type === "search_blur") {
return {
focused: false
}
}
return state;
}

2.在src-store-reducer.js中,将分开的小reducer通过 combineReducers 结合成完整的一个reducer

1
2
3
4
5
6
7
8
import { combineReducers } from "redux";
import headerReducer from "../common/header/store/reducer";

const reducer = combineReducers({
header: headerReducer
})

export default reducer;

此时可以看到focused不是在顶层state中了,她在header(也就是我们使用combineReducers时规定的键中)下,focused值会变,但动画效果不生效
效果

3.由于原本放在reducer中的数据迁移到了小reducer中,所以**在组件中规定store和props的映射的函数(connect方法的参数2mapStateToProps())中的state.focused也应当改为state.header.focused**,组件才能获取到store中的数据:

1
2
3
4
5
const mapStateToProps = (state) => {
return {
focused: state.header.focused
}
}

4.优化:在 总reducer 中引入 分reducer 的路径太长,此时可以在 分reducer 的文件夹中新建index.js用于 “引入、输出 分reducer”,这样在 总reducer 中引入 分reducer 时就可以直接从这个index中引入了。

common-header-store新建index.js:

1
2
3
import reducer from "./reducer";

export { reducer };

src-store-reducer.js中引用路径就变短(**as是ES6的语法,用于起别名。**否则两个reducer会报错):

1
2
3
4
5
6
7
8
import { combineReducers } from "redux";
import { reducer as headerReducer } from "../common/header/store";

const reducer = combineReducers({
header: headerReducer
})

export default reducer;
,