一步一步学习 ReactNative + Redux(0)

写在开始

研究 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,
    },
});

看看效果如何,如果看到以下页面,则说明咱们还没有出现任何错误。

Paste_Image.png

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,
    },
});

运行项目,如果显示如下子,则说明咱们还没出错:

Paste_Image.png

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了。

Paste_Image.png

5、过滤状态显示
略... (相信能够自己完成)

下篇中,将会使用 Redux !!!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容