一步一步學(xué)習(xí) ReactNative + Redux(0)

寫在開始

研究 ReactNative 有一小段時(shí)間了,之前就聽過狀態(tài)管理 Redux 的大名,由于種種原因,沒深入了解。
這兩天有些許的時(shí)間,本想看看Redux,然鵝,一臉懵B ...
然后,經(jīng)過三天三夜的大戰(zhàn),漸入佳境。固,將一些心法心得記錄下來。
這是開篇,會慢慢演化如何使用 ReactNative + Redux 。
注意:
這里并沒有使用 Redux
這里并沒有使用 Redux
這里并沒有使用 Redux

源碼:https://github.com/eylu/web-lib/tree/master/ReactReduxDemo/app_step0

案例

我們還是以 TODO 案例來敘述整個(gè)過程。
功能需求:
TODO 列表,展示 TODO 項(xiàng),點(diǎn)擊后,切換狀態(tài)(完成,文字添加刪除線,未完成:沒有刪除線);
TODO 新增,一個(gè)文本框,一個(gè)按鈕,輸入文字后,點(diǎn)擊按鈕添加到 TODO 列表,狀態(tài)默認(rèn)是未完成;
TODO 篩選,三個(gè)按鈕 All、Undo、Finish,點(diǎn)擊按鈕后,列表會顯示不同的狀態(tài)的 TODO 項(xiàng)。

開發(fā)

在明確功能需求之后,我們來進(jìn)行 React Native 項(xiàng)目搭建與開發(fā)。
開發(fā)環(huán)境需要 Node.js、JDK、Android SDK,這些安裝不再贅述。

一、創(chuàng)建項(xiàng)目
$ react-native init ReactNativeDemo

等待些許時(shí)間,項(xiàng)目將會初始化完成,項(xiàng)目目錄如下:

|--ReactNativeDemo
    |--__tests__
    |--android
    |--ios
    |--node_modules
    |--index.android.js
    |--index.ios.js
    |--package.json
    |--...

package.json 文件為本項(xiàng)目依賴包:

{
    "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"
    }
}

此時(shí),已然可以運(yùn)行項(xiàng)目(可以直接安裝在Android手機(jī),或者使用iOS模擬器。)

$ react-native run-android 
$ react-native run-ios
二、編寫代碼

接下來,我們開始完成功能開發(fā)。
為了使項(xiàng)目結(jié)構(gòu)看起來整潔一些,我們對項(xiàng)目進(jìn)行簡單的模塊化區(qū)分,讓人(項(xiàng)目成員)讀起來,簡明知意。

1、準(zhǔn)備工作
首先,創(chuàng)建一個(gè) app 文件夾,我們將會把所有的代碼都放在此文件夾中,在 index.android(ios).js 中引用。
然后,我們在 app 文件夾中創(chuàng)建一個(gè)入口文件 index.js 和兩個(gè)文件夾 containers(容器文件夾,也就是頁面)、components(組件文件夾,頁面中用到的所有組件均放在這里)。
我們的項(xiàng)目結(jié)構(gòu)看起來是這樣:

|--ReactNativeDemo
    |--__tests__
    |--android
    |--app
        |--components
        |--containers
        |--index.js
    |--ios
    |--node_modules
    |--index.android.js
    |--index.ios.js
    |--package.json
    |--...

我們現(xiàn)在需要看看我們的項(xiàng)目結(jié)構(gòu)是否能正常使用呢,寫一些簡單的代碼來測試看看。

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

看看效果如何,如果看到以下頁面,則說明咱們還沒有出現(xiàn)任何錯(cuò)誤。

Paste_Image.png

2、顯示 TODO 列表
我們會在入口文件 ReactNativeDemo/app/index.js 中引入一個(gè)(或多個(gè))容器組件(HomeContainer),容器組件再引入多個(gè)子組件展示信息。
容器組件:
1)提供數(shù)據(jù)給子組件(通過props);
2)數(shù)據(jù)處理;
子組件:
1)展示數(shù)據(jù)(子組件一般都是通過父組件props獲取數(shù)據(jù),它并不關(guān)心數(shù)據(jù)來源);
2)部分?jǐn)?shù)據(jù)處理與調(diào)用父組件數(shù)據(jù)處理方法(通過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),并有一個(gè)初始狀態(tài)todoList (包涵3個(gè) TODO 項(xiàng)),還要渲染子組件(TodoListComponent),通過props傳遞數(shù)據(jù)給子組件(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);
        // 初始狀態(tài) 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 項(xiàng)目了。
新建文件 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,
    },
});

運(yùn)行項(xiàng)目,如果顯示如下子,則說明咱們還沒出錯(cuò):

Paste_Image.png

3、TODO 處理
為 TODO 項(xiàng)目添加點(diǎn)擊事件,點(diǎn)擊后可切換此 TODO 的狀態(tài)(未完成、已完成)。
我們在這里引入 TouchableOpacity,可以添加點(diǎn)擊事件。
由于 TODO 列表的數(shù)據(jù)是容器組件HomeContainer通過 props 傳遞給子組件 TodoListComponent 的,所以,點(diǎn)擊事件要通過 props 傳回給容器組件,處理數(shù)據(jù)后,更改狀態(tài) state ,子組件 TodoListComponent 接受新數(shù)據(jù)以更新顯示狀態(tài)。

ReactNativeDemo/app/components/todo-list.component.js 文件:

import React, { Component } from 'react';
import {
    Text,
    View,
    StyleSheet,
    TouchableOpacity,    // 引入新組件,可以添加點(diǎn)擊功能
} from 'react-native';


export default class TodoListComponent extends Component{
    constructor(props){
        super(props);
        this.state = {
            todoList: this.props.todoList||[],
        };
    }

    componentWillReceiveProps(newProps){     // 接受新數(shù)據(jù),更新狀態(tài)顯示
        this.setState({
            todoList: newProps.todoList || [],
        });
    }

    toggleTodo(index){     // 點(diǎn)擊事件,傳回父組件,執(zhí)行相應(yīng)處理
        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 列表組件點(diǎn)擊 TODO 項(xiàng),傳回給容器組件做數(shù)據(jù)處理,更新 state,數(shù)據(jù)會自動傳遞給 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){    // 數(shù)據(jù)處理,切換 todo 狀態(tài),更新 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>
        );
    }
}

運(yùn)行項(xiàng)目,點(diǎn)擊每個(gè) TODO 項(xiàng),如果顯示一條刪除線,則說明咱們成功了。

4、添加 TODO 項(xiàng)
我們將添加 TODO 功能也做成一個(gè)子組件 TodoFormComponent ,引入到容器組件 HomeContainer 中。
子組件 TodoFormComponent 包含一個(gè)輸入框 TextInput、一個(gè)按鈕 Button
在輸入框中輸入文本,會存儲到 state 中;
點(diǎn)擊按鈕,獲取到 state 中的文本,通過 props 傳遞給父組件做數(shù)據(jù)處理,顯示在 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需要做數(shù)據(jù)處理,切換 TODO 狀態(tài)。

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){       // 執(zhí)行添加方法,更新數(shù)據(jù)
        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>
        );
    }
}

運(yùn)行項(xiàng)目,看看是否顯示文本框和按鈕了呢?輸入內(nèi)容,點(diǎn)擊按鈕,看看是否可以在 TODO 列表下面顯示新的 TODO 項(xiàng)了?OK了。

Paste_Image.png

5、過濾狀態(tài)顯示
略... (相信能夠自己完成)

下篇中,將會使用 Redux !!!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容