Last active
October 21, 2017 00:17
-
-
Save wd/7935c3fb20f2517280b8e050796310b3 to your computer and use it in GitHub Desktop.
React-native react-redux with react-navigation StackNavigator single page demo
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 一个简单的 RN 应用,有 2 个页面,使用了 react-navigation 的 StackNavigator 来做界面管理 | |
* 为了说明如何使用 redux,以及如何让 redux 和 StackNavigator 配合 | |
* 为了容易理解,把所有内容都放到了一个页面里面,实际开发的时候不要这么做 | |
* 参考: | |
* https://github.com/jackielii/simplest-redux-example | |
* http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_three_react-redux.html | |
*/ | |
import React, { Component } from 'react'; | |
import { | |
StyleSheet, | |
Text, | |
View, | |
Button | |
} from 'react-native'; | |
import { Provider, connect } from 'react-redux'; | |
import { createStore, combineReducers } from 'redux'; | |
import { StackNavigator, addNavigationHelpers } from 'react-navigation'; | |
// Home 页面,UI 组件 | |
class MyHome extends Component { | |
constructor(props) { | |
super(props); | |
console.log('init home, props', props); | |
} | |
_nextPage() { | |
// navigation 依然在 this.props 里面获取,和不用 redux 的时候用法一样 | |
let {navigation} = this.props; | |
navigation.navigate("App"); | |
} | |
render() { | |
// 所有的传递过来的状态,都需要从 this.props.screenProps 里面读取 (4) | |
// 我这里给不同页面的 action 取了各自的命名空间,避免冲突,也可以直接所有 action 都在一个命名空间,这块我还在摸索如何处理比较好 (5) | |
let {onIncButtonClicked} = this.props.screenProps.MyAppActions; | |
// 界面有两个按钮,一个用来增加另外一个页面的计数器,一个用来访问下一个页面 | |
return ( | |
<View style={styles.container}> | |
<Button title="Inc counter" onPress={onIncButtonClicked}></Button> | |
<Button title="Next page" onPress={()=>this._nextPage()}></Button> | |
</View> | |
) | |
} | |
} | |
// 这个组件只是用来测试就算一个 props 传递给子组件,在 props 被修改的时候也会被自动刷新 | |
class ShowText extends Component { | |
render() { | |
let {counter} = this.props; | |
return ( | |
<Text>{counter}</Text> | |
) | |
} | |
} | |
// App 页面,UI 组件 | |
class MyApp extends Component { | |
constructor(props) { | |
super(props); | |
console.log('init App, props', props); | |
} | |
componentWillReceiveProps(newProps) { | |
console.log('myapp recive props', newProps); | |
} | |
render() { | |
// 组件的 state/props 获取,有自己的命名空间 (1) | |
let {counter} = this.props.screenProps.MyApp; | |
// 组件的 action props (5) | |
let {onIncButtonClicked, onDecButtonClicked} = this.props.screenProps.MyAppActions; | |
// 界面有一个计数器的结果,两个按钮 | |
return ( | |
<View style={styles.container}> | |
<ShowText counter={counter} /> | |
<Button title="Inc counter" onPress={onIncButtonClicked}></Button> | |
<Button title="Dec counter" onPress={onDecButtonClicked}></Button> | |
</View> | |
) | |
} | |
} | |
// 初始化 StackNavigator,定义页面路由 | |
let AppNavigator = StackNavigator({ | |
Home: { | |
screen: MyHome | |
}, | |
App: { | |
screen: MyApp | |
} | |
}); | |
// 包装一下 StackNavigator,因为有些参数需要定制一下 | |
class MyStackNavigator extends Component { | |
constructor(props) { | |
super(props); | |
console.log("inside MyStackNavigator", props); | |
} | |
render() { | |
// screenProps: 使用这个往所有的页面传递 props,这个是和直接使用 redux 不同的地方 (4) | |
// navigation: 因为使用 redux 之后,就不会直接操作 this.state 了,所以得告诉 StackNavigator dispatch 方法和 state 从哪里读取 | |
return ( | |
<AppNavigator | |
screenProps={this.props} | |
navigation={addNavigationHelpers({ | |
dispatch: this.props.dispatch, // 通过 action props 定义 (2) | |
state: this.props.nav, // 通过 state props 定义 (3) | |
})} /> | |
) | |
} | |
} | |
// 定义 state 和 props 的关系,所有 redux 应用都需要 (6) | |
let mapStateToProps = (state, ownProps) => { | |
console.log("inside mapstate to props", state, ownProps); | |
return { | |
// 这两个是不同的命名空间,和上面你使用的时候的路径对应 (1) | |
"MyApp": state.MyApp, | |
"MyHome": state.MyHome, | |
// 定义 StackNavigator 的 state (3) | |
"nav": state.nav | |
} | |
}; | |
// 定义 action 和 props 的关系,所有 redux 应用都需要 | |
let mapDispatchToProps = (dispatch, ownProps) => { | |
console.log("inside map dispath to props"); | |
return { | |
// 这两个也是不同的命名空间,和上面使用的时候路径对应 (5) | |
'MyAppActions': { | |
onIncButtonClicked: () => { | |
let action = { | |
type: "INC_COUNTER", | |
payload: 1 | |
}; | |
dispatch(action); | |
}, | |
onDecButtonClicked: () => { | |
let action = { | |
type: "DEC_COUNTER", | |
payload: -1 | |
}; | |
dispatch(action); | |
} | |
}, | |
'MyHomeActions': { | |
onNextButtonClicked: () => { | |
let action = { | |
type: "NEXT_PAGE" | |
}; | |
dispatch(action); | |
} | |
}, | |
// 定义 StackNavigator 的 action props (2) | |
'dispatch': dispatch | |
} | |
} | |
// 定义 home 页面的 reducer,不过因为那个页面唯一的一个 action 是触发别的页面的动作的,所以这个 reducer 其实也可以没有 | |
// 所以从这里也能看出来,reducer 并不一定按照页面去分 | |
let homeReducer = (state, action) => { | |
console.log("inside home reducer", state, action); | |
return state || {}; | |
}; | |
// 定义一个初始化的 state | |
let myAppInitState = { 'counter': 10}; | |
// 定义 app 页面的 reducer | |
let myAppReducer = (state = myAppInitState, action) => { | |
// 收到的 state 实际上只是自己命名空间下的 (6) | |
console.log("inside myAppReducer", state, action); | |
let myState = state; | |
// 需要处理的 action 的逻辑 | |
// 要注意,一个 action 被触发的时候,所有的 reducer 都会被调用,所以其实更像是订阅自己想要处理的 action | |
switch (action.type) { | |
case "DEC_COUNTER": | |
case "INC_COUNTER": | |
// 如果修改了 state,必须要返回一个新的对象,不能直接在原对象上修改,否则 state 变化不会触发组件的刷新 | |
return Object.assign({}, myState, { | |
'counter': myState.counter + action.payload | |
}); | |
default: | |
return state; | |
} | |
}; | |
// 定义一个 StackNavigator 用到的初始化状态,这个很重要 | |
const initialState = AppNavigator.router.getStateForAction(AppNavigator.router.getActionForPathAndParams('Home')); | |
// 定义 StackNavigator 的 reducer,代码直接复制来的 | |
const navReducer = (state = initialState, action) => { | |
console.log("inside nav reducer", state, action); | |
const nextState = AppNavigator.router.getStateForAction(action, state); | |
// Simply return the original `state` if `nextState` is null or undefined. | |
return nextState || state; | |
}; | |
// 创建 store | |
let store = createStore(combineReducers({ | |
// 这里的 MyApp 等和前面定义 mapStateToProps 的地方对应 (6) | |
// 这里也是导致 reducer 收到的 state 只有自己命名空间下数据的一个原因 (6) | |
MyApp: myAppReducer, | |
MyHome: homeReducer, | |
nav: navReducer | |
})); | |
// 让 redux 加持一下,保佑 | |
let App = connect(mapStateToProps, mapDispatchToProps)(MyStackNavigator); | |
// 其他的就是比较常见的 redux 的逻辑了,另外需要说明的是实际使用的时候,肯定会做页面拆分,如何拆分可能都会有不同的看法,我也还在摸索 | |
export default class Root extends Component<{}> { | |
constructor(props) { | |
super(props); | |
} | |
render() { | |
return ( | |
<Provider store={store}> | |
<App prop1="prop1" /> | |
</Provider> | |
); | |
} | |
} | |
const styles = StyleSheet.create({ | |
container: { | |
flex: 1, | |
justifyContent: 'center', | |
alignItems: 'center', | |
backgroundColor: '#F5FCFF', | |
}, | |
welcome: { | |
fontSize: 20, | |
textAlign: 'center', | |
margin: 10, | |
}, | |
instructions: { | |
textAlign: 'center', | |
color: '#333333', | |
marginBottom: 5, | |
}, | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
rev1 is a simple react-redux app, rev2 is a example for react-redux work with react-navigation