寫在開始
研究 ReactNative 有一小段時間了,之前就聽過狀態管理 Redux 的大名,由于種種原因,沒深入了解。
這兩天有些許的時間,本想看看Redux,然鵝,一臉懵B ...
然后,經過三天三夜的大戰,漸入佳境。固,將一些心法心得記錄下來。
這是開篇,會慢慢演化如何使用 ReactNative + Redux 。
注意:
這里并沒有使用 Redux
這里并沒有使用 Redux
這里并沒有使用 Redux
源碼:https://github.com/eylu/web-lib/tree/master/ReactReduxDemo/app_step0
案例
我們還是以 TODO 案例來敘述整個過程。
功能需求:
TODO 列表,展示 TODO 項,點擊后,切換狀態(完成,文字添加刪除線,未完成:沒有刪除線);
TODO 新增,一個文本框,一個按鈕,輸入文字后,點擊按鈕添加到 TODO 列表,狀態默認是未完成;
TODO 篩選,三個按鈕 All、Undo、Finish,點擊按鈕后,列表會顯示不同的狀態的 TODO 項。
開發
在明確功能需求之后,我們來進行 React Native 項目搭建與開發。
開發環境需要 Node.js、JDK、Android SDK,這些安裝不再贅述。
一、創建項目
$ react-native init ReactNativeDemo
等待些許時間,項目將會初始化完成,項目目錄如下:
|--ReactNativeDemo
|--__tests__
|--android
|--ios
|--node_modules
|--index.android.js
|--index.ios.js
|--package.json
|--...
package.json 文件為本項目依賴包:
{
"name": "ReactNativeDemo",
"version": "0.0.1",
"private": true,
"scripts": {
"start": "node node_modules/react-native/local-cli/cli.js start",
"test": "jest"
},
"dependencies": {
"react": "15.4.1",
"react-native": "0.38.0"
},
"jest": {
"preset": "react-native"
},
"devDependencies": {
"babel-jest": "17.0.2",
"babel-preset-react-native": "1.9.0",
"jest": "17.0.3",
"react-test-renderer": "15.4.1"
}
}
此時,已然可以運行項目(可以直接安裝在Android手機,或者使用iOS模擬器。)
$ react-native run-android
$ react-native run-ios
二、編寫代碼
接下來,我們開始完成功能開發。
為了使項目結構看起來整潔一些,我們對項目進行簡單的模塊化區分,讓人(項目成員)讀起來,簡明知意。
1、準備工作
首先,創建一個 app 文件夾,我們將會把所有的代碼都放在此文件夾中,在 index.android(ios).js
中引用。
然后,我們在 app 文件夾中創建一個入口文件 index.js
和兩個文件夾 containers(容器文件夾,也就是頁面)、components(組件文件夾,頁面中用到的所有組件均放在這里)。
我們的項目結構看起來是這樣:
|--ReactNativeDemo
|--__tests__
|--android
|--app
|--components
|--containers
|--index.js
|--ios
|--node_modules
|--index.android.js
|--index.ios.js
|--package.json
|--...
我們現在需要看看我們的項目結構是否能正常使用呢,寫一些簡單的代碼來測試看看。
ReactNativeDemo/index.ios.js
文件
import React, {
Component
} from 'react';
import {
AppRegistry,
StyleSheet,
View
} from 'react-native';
import RootWrapper from './app/index'; // 引入入口文件
export default class ReactNativeDemo extends Component {
render() {
return (
<View style={styles.container}>
<RootWrapper />
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5FCFF',
}
});
AppRegistry.registerComponent('ReactNativeDemo', () => ReactNativeDemo);
新建 ReactNativeDemo/app/index.js
文件
import React, { Component } from 'react';
import {
View,
Text,
StyleSheet,
} from 'react-native';
export default class RootWrapper extends Component{
render(){
return (
<View style={styles.wrapper}>
<Text>Hello , React Native !</Text>
</View>
);
}
}
const styles = StyleSheet.create({
wrapper: {
flex: 1,
marginTop: 20,
},
});
看看效果如何,如果看到以下頁面,則說明咱們還沒有出現任何錯誤。
2、顯示 TODO 列表
我們會在入口文件 ReactNativeDemo/app/index.js
中引入一個(或多個)容器組件(HomeContainer
),容器組件再引入多個子組件展示信息。
容器組件:
1)提供數據給子組件(通過props
);
2)數據處理;
子組件:
1)展示數據(子組件一般都是通過父組件props
獲取數據,它并不關心數據來源);
2)部分數據處理與調用父組件數據處理方法(通過props
);
ReactNativeDemo/app/index.js
文件,我們稍作修改:
import React, { Component } from 'react';
import {
View,
Text,
StyleSheet,
} from 'react-native';
import HomeContainer from './containers/home.container'; // 引入容器組件
export default class RootWrapper extends Component{
render(){
return (
<View style={styles.wrapper}>
<HomeContainer />
</View>
);
}
}
const styles = StyleSheet.create({
wrapper: {
flex: 1,
marginTop: 20,
},
});
接下來,我們編寫容器組件(HomeContainer
), HomeContainer
會引入子組件(TodoListComponent
),并有一個初始狀態todoList
(包涵3個 TODO 項),還要渲染子組件(TodoListComponent
),通過props
傳遞數據給子組件(TodoListComponent
)。
新建文件 ReactNativeDemo/app/containers/home.container.js
,如下:
import React, { Component } from 'react';
import {
View,
} from 'react-native';
import TodoListComponent from '../components/todo-list.component'; // 引入子組件
export default class HomeContainer extends Component{
constructor(props){
super(props);
// 初始狀態 todoList
this.state = {
todoList: [{title:'Eat',status:false},{title:'Play',status:false},{title:'Sleep',status:false} ],
};
}
render(){
return (
<View>
<TodoListComponent todoList={this.state.todoList} />
</View>
);
}
}
這里,我們需要展示我們的 TODO 項目了。
新建文件 ReactNativeDemo/app/components/todo-list.component.js
,如下:
import React, { Component } from 'react';
import {
Text,
View,
StyleSheet,
} from 'react-native';
export default class TodoListComponent extends Component{
constructor(props){
super(props);
this.state = {
todoList: this.props.todoList,
};
}
render(){
return (
<View style={styles.wrapper}>
{this.state.todoList.map((todo, index)=>{
var finishStyle = {textDecorationLine:'line-through', color:'gray'};
return (
<Text style={[styles.todo,todo.status&&finishStyle]}>{todo.title}</Text>
);
})}
</View>
);
}
}
TodoListComponent.defaultProps = {
todoList: [],
}
const styles = StyleSheet.create({
wrapper: {
paddingHorizontal: 20,
},
todo: {
paddingVertical: 5,
},
});
運行項目,如果顯示如下子,則說明咱們還沒出錯:
3、TODO 處理
為 TODO 項目添加點擊事件,點擊后可切換此 TODO 的狀態(未完成、已完成)。
我們在這里引入 TouchableOpacity
,可以添加點擊事件。
由于 TODO 列表的數據是容器組件HomeContainer
通過 props
傳遞給子組件 TodoListComponent
的,所以,點擊事件要通過 props
傳回給容器組件,處理數據后,更改狀態 state
,子組件 TodoListComponent
接受新數據以更新顯示狀態。
ReactNativeDemo/app/components/todo-list.component.js
文件:
import React, { Component } from 'react';
import {
Text,
View,
StyleSheet,
TouchableOpacity, // 引入新組件,可以添加點擊功能
} from 'react-native';
export default class TodoListComponent extends Component{
constructor(props){
super(props);
this.state = {
todoList: this.props.todoList||[],
};
}
componentWillReceiveProps(newProps){ // 接受新數據,更新狀態顯示
this.setState({
todoList: newProps.todoList || [],
});
}
toggleTodo(index){ // 點擊事件,傳回父組件,執行相應處理
this.props.toggleTodo && this.props.toggleTodo(index);
}
render(){
return (
<View style={styles.wrapper}>
{this.state.todoList.map((todo, index)=>{
var finishStyle = {textDecorationLine:'line-through', color:'gray'};
return (
<TouchableOpacity onPress={()=>{this.toggleTodo(index)}}>
<Text style={[styles.todo,todo.status&&finishStyle]}>{todo.title}</Text>
</TouchableOpacity>
);
})}
</View>
);
}
}
const styles = StyleSheet.create({
wrapper: {
paddingHorizontal: 20,
},
todo: {
paddingVertical: 5,
},
});
TODO 列表組件點擊 TODO 項,傳回給容器組件做數據處理,更新 state
,數據會自動傳遞給 TODO 列表組件 TodoListComponent
,更新顯示。
ReactNativeDemo/app/containers/home.container.js
文件,稍作修改:
import React, { Component } from 'react';
import {
View,
} from 'react-native';
import TodoListComponent from '../components/todo-list.component';
export default class HomeContainer extends Component{
constructor(props){
super(props);
this.state = {
todoList: [{title:'Eat',status:false},{title:'Play',status:false},{title:'Sleep',status:false} ],
};
}
toggleTodo(index){ // 數據處理,切換 todo 狀態,更新 state
var todoList = this.state.todoList;
var todo = todoList[index];
if(todo){
todo.status = !todo.status;
this.setState({
todoList: todoList,
})
}
}
render(){
return (
<View>
<TodoListComponent todoList={this.state.todoList} toggleTodo={(index)=>{this.toggleTodo(index)}} />
</View>
);
}
}
運行項目,點擊每個 TODO 項,如果顯示一條刪除線,則說明咱們成功了。
4、添加 TODO 項
我們將添加 TODO 功能也做成一個子組件 TodoFormComponent
,引入到容器組件 HomeContainer
中。
子組件 TodoFormComponent
包含一個輸入框 TextInput
、一個按鈕 Button
。
在輸入框中輸入文本,會存儲到 state
中;
點擊按鈕,獲取到 state
中的文本,通過 props
傳遞給父組件做數據處理,顯示在 TODO 列表組件 TodoListComponent
中。
新建文件 ReactNativeDemo/app/components/todo-form.component.js
,代碼如下:
import React, { Component } from 'react';
import {
View,
TextInput,
Button,
StyleSheet,
} from 'react-native';
export default class TodoFormComponent extends Component{
constructor(props){
super(props);
this.state = {
todo: null,
};
}
addTodo(){
this.props.addTodo && this.props.addTodo(this.state.todo);
}
setTodo(text){
this.setState({
todo: text
});
}
render(){
return (
<View style={styles.wrapper}>
<TextInput style={styles.input} onChangeText={(text)=>{this.setTodo(text)}} />
<Button title="添加" onPress={()=>this.addTodo()} />
</View>
);
}
}
const styles = StyleSheet.create({
wrapper: {
paddingHorizontal: 10,
flexDirection: 'row',
},
input: {
height: 30,
borderColor: 'gray',
borderWidth: 1,
flex: 1,
},
});
寫完添加組件的功能了,我們還要在容器組件 HomeContainer
中引入,并且,容器組件 HomeContainer
需要做數據處理,切換 TODO 狀態。
ReactNativeDemo/app/containers/home.container.js
文件,稍作修改:
import React, { Component } from 'react';
import {
View,
} from 'react-native';
import TodoFormComponent from '../components/todo-form.component'; // 引入添加組件
import TodoListComponent from '../components/todo-list.component';
export default class HomeContainer extends Component{
constructor(props){
super(props);
this.state = {
todoList: [{title:'Eat',status:false},{title:'Play',status:false},{title:'Sleep',status:false} ],
};
}
addTodo(text){ // 執行添加方法,更新數據
var todoList = this.state.todoList;
todoList.push({
title: text,
status: false,
});
this.setState({
todoList: todoList,
})
}
toggleTodo(index){
var todoList = this.state.todoList;
var todo = todoList[index];
if(todo){
todo.status = !todo.status;
this.setState({
todoList: todoList,
})
}
}
render(){
return (
<View>
<TodoFormComponent addTodo={(text)=>{this.addTodo(text)}} />
<TodoListComponent todoList={this.state.todoList} toggleTodo={(index)=>{this.toggleTodo(index)}} />
</View>
);
}
}
運行項目,看看是否顯示文本框和按鈕了呢?輸入內容,點擊按鈕,看看是否可以在 TODO 列表下面顯示新的 TODO 項了?OK了。
5、過濾狀態顯示
略... (相信能夠自己完成)
下篇中,將會使用 Redux !!!