美文网首页redux学习react-nativeReactNative系列
一步一步学习 ReactNative + Redux(0)

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

作者: EyluWang | 来源:发表于2016-12-12 16:21 被阅读626次

    写在开始

    研究 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 !!!

    相关文章

      网友评论

      • 9b96d89b9432:请教下: constructor(props){
        super(props);
        this.state = {
        todoList: [{title:'Eat',status:false},{title:'Play',status:false},{title:'Sleep',status:false} ],
        };
        } 这里的 state props 于使用redux 之后的操作state props 的区别
        9b96d89b9432:@EyluWang 谢了
        EyluWang:@西结古原 他们只是数据,没有太多的区别,唯一区别是存放的位置不一样了。这里是存在组件里。redux里是存在store里。
        9b96d89b9432:主要是对这个 state props 在实际项目开发中的用法不太清晰... 麻烦楼主讲一下
      • 无敌小lengxin:请问addTodo里面除了调用“ this.props.addTodo(this.state.todo)”这个方法,为什么还要加一个“ this.props.addTodo” 作为条件判断??请指教
        无敌小lengxin:@EyluWang 明白,谢谢指点。。期待您的更多干货好
        EyluWang:@pengpuxin2015 判断addTodo这个props 是否存在,要保证程序的正确运行。
      • c829964daa3a:写的不错
        EyluWang:客气了

      本文标题:一步一步学习 ReactNative + Redux(0)

      本文链接:https://www.haomeiwen.com/subject/muukmttx.html