写在开始
研究 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 !!!