在 iOS 开发领域工作3年半了,都说金三银四,今天四月最后一天,工作还没有落实。想当初还是个小白的时候,一天能安排4、5个面试,如今找工作如此之难,真是慌得一比。当然,不能怨天尤人,总归是自己实力不够硬朗。
最近闲来无事,捡起了快被自己忘的一干二净的 ReactNative ,简单了写了个小项目,算是温故知新了。
Redux
Redux 单项数据流框架,其特点是严格的数据流控制,开发过程中应遵循3个原则:
- 唯一数据源
- 保持状态只读
- 数据改变只能通过纯函数
1. 唯一数据源
Redux 应用中应保持数据源的唯一性,说白了整个应用中只保持一个 Store,所有组件的数据源就是这个 Store 上的状态,Store 是个树形结构,往往某一个组件或者模块的数据来源于 Store 上的某个节点。
2. 保持状态只读
Redux 强调要改变 Store 的状态只能通过 action ,action 返回对象,提供给 Redux 完成新的状态的组装。
3. 数据改变只能通过纯函数
这里的纯函数就是 Reducer ,其函数签名包含两个参数 state、action,顾名思义就是通过 action 去改变 state,一般来说是返回一个新的 state,其函数签名大致如下export default (state = initialState, action) => {}
。
总结一下
Reudx 包含 Store、State、Reudcer、action,主要为这四部分组成:
- Store
一个全局的对象, 其包含 Reudcer ,是一个树形结构,每个 Reducer 算一个节点。 - Reducer
返回一个新的状态,简单来说,通过什么样的 action 产生一个改变了某一个节点的 State - State
状态树,同样树形结构,可以理解为 Reducer 下面的子节点,state 的设计应尽量扁平,一个模块控制一个状态节点、避免冗余数据。 - action
返回一个对象, 供 Reducer 使用。 action 函数返回的对象大致如下{ type: actionType, data: ooxx };
这里说一下 actionType:一个常亮,用来区分不同的 action
聪明组件&傻瓜组件(容器组件&展示组件)
所谓聪明、傻瓜只是相对来说,同样也叫容器组件和展示组件。 鉴于专业性,下文一律采用容器组件和展示组件的叫法。容器组件负责将 Store 中的状态,通过 props 传递给展示组件,展示组件只负责渲染页面,无需持有状态。将一个组件拆分为容器组件和展示组件是设计 React 组件的一种模式,和 Redux 无关。
前两者的结合 react-redux
上面讲到,Redux 负责管理状态,组件拆分为容器组件和展示组件。容器组件需要状态,状态来自哪呢?当然是 Redux 。 故,可以将 Redux 和组件做一个结合,达到更好的效果,那就是 react-redux,他帮助开发者抽取了可复用的容器组件,开发者只需关注展示组件即可。
相比于 Redux ,react-redux 多了 Provider 和 connect 两部分
Provider
提供了 Store 的容器组件,Provider 应位于根组件最顶层,管理所有子组件。其中会检查这一次渲染时 Store 代表的 props 和上次是否一致,这样做避免了多次渲染用了不同的 Store ,所以项目中应只有一个 Store。react-redux 提供了创建 Store 的方法。
connect
一个函数,负责展示组件和容器组件的连接。大致是这样export default connect(mapStateToProps, mapDispatchToProps)(SearchBar)
这里边其实是两次函数的执行,首先 connect 函数执行并返回了另一个函数然后执行,参数是展示组件。
- mapStateToProps
一个返回对象的函数,可选的。通过函数签名可以知道是将 Store 中的某一个 State 转化为展示组件所需要的 props,决定暂时组件显示什么样的数据。 - mapDispatchToProps
一个返回对象的函数,可选的。展示组件不能只负责展示,当然也有一定的交互,也就是触发 action 。在 react-redux 中触发一个 action 是通过 dispatch 执行的。所以这个函数是将 dispatch 转换为 props 供展示组件使用。
总结一下
不难理解 react-redux 告诉我们,展示组件仅仅负责展示,不需要持有任何状态,展示组件的所有 state、action 全部来源于 props ,容器组件通过 props 传递给展示组件。
示例代码
Demo地址
- actionType
export const HOMEPAGE_SHOWSEARCHBAR = 'HOMEPAGE/SHOWSEARCHBAR';
export const HOMEPAGE_MENU_PAGECHANGE = 'HOMEPAGE/MENU/PAGECHANGE';
- action
export const searchBarFetch = (text) => ({
type: HOMEPAGE_FETCH_SEARCHBAR,
text: text
});
export const updateHomePageMenuPage = (page) => ({
type: HOMEPAGE_MENU_PAGECHANGE,
currentPage: page
});
- State (我自认为不是很扁平,懒着改了)
const initialState = {
homepage: {
menuInfo: {
items: common.menuInfos,
currentPage: 0
},
gridInfos: [],
sections: [{
title: '',
data: []
}],
},
searchBar: {
text: '搜一下'
}
};
- Reducer
Reducer 可以是多个,一般一个模块一个 Reducer
export default (state = initialState, action) => {
switch (action.type) {
case HOMEPAGE_FETCH_SEARCHBAR:
{
return {
...state,
searchBar: {
text: action.text
}
};
}
case HOMEPAGE_MENU_PAGECHANGE:
{
return {
...state,
homepage: {
menuInfo: {
items: state.homepage.menuInfo.items,
currentPage: action.currentPage
},
gridInfos: state.homepage.gridInfos,
sections: state.homepage.sections
}
}
}
}
return state;
};
- Store
react-redux 提供了 合并多个 Rducer 的方法,和创建 Store 的方法
const reducers = combineReducers({
homepageReudcer
});
export default createStore(reducers);
- 展示组件
class HomeMenu extends Component {
render() {
const {menuInfos, currentPage} = this.props;
const items = menuInfos.map(({title, icon}) => (<HomeMenuItem title={title} icon={icon} key={title}/>));
const pageCount = Math.ceil(items.length / 10);
let menuViews = [];
for (let i = 0; i < pageCount; i++) {
const itemSlices = items.slice(i * 10, i * 10 + 10);
const view = <View style={styles.itemsView} key={i}>
{itemSlices}
</View>
menuViews.push(view);
}
return (
<View style={styles.container}>
<ScrollView
horizontal
pagingEnabled={true}
showsHorizontalScrollIndicator={false}
onScroll={this._onScroll}>
{menuViews}
</ScrollView>
<PageControl
style={styles.pageControl}
numberOfPages={pageCount}
currentPage={currentPage}
currentPageIndicatorTintColor={color.primary}
pageIndicatorTintColor={color.gray}/>
<View style={styles.line}/>
<HomeGridView/>
<View style={styles.line}/>
</View>
);
}
_onScroll = (event) => {
const x = event.nativeEvent.contentOffset.x;
const page = Math.round(x / common.screen.width);
const {currentPage, setCurrentPage} = this.props;
if (currentPage !== page) {
setCurrentPage(page);
}
}
}
const styles = StyleSheet.create({
container: {
backgroundColor: 'white',
},
itemsView: {
flexDirection: 'row',
flexWrap: 'wrap',
width: common.screen.width
},
pageControl: {
margin: 10
},
line: {
backgroundColor: color.paper,
width: common.screen.width,
height: 10,
}
});
const mapStateToProps = (state) => ({menuInfos: state.homepageReudcer.homepage.menuInfo.items, currentPage: state.homepageReudcer.homepage.menuInfo.currentPage});
const mapDispatchToProps = (dispatch) => ({
setCurrentPage: (page) => {
dispatch(updateHomePageMenuPage(page));
}
})
export default connect(mapStateToProps, mapDispatchToProps)(HomeMenu)
网友评论