react-native集成Websocket,protobuf,redux-saga的实现

一、 开始前的说明

本文从最开始一步一步搭建一个react-native + redux-saga + websocket + protobuf的项目。项目完全是按照规范的应用开发目录构建。

  1. 你将学到的知识
    1.1 如何创建一个 react-native项目,react-native项目工程目录搭建。
    1.2 如何集成 redux 。
    1.3 如何集成 redux-saga。
    1.4 如何使用 websocket。
    1.5 如何使用protobufjs。

1.1 创建一个react-native项目

(创建项目非常简单,如果你不会或者还没有搭建环境 请参考 点击前往
在cmd控制台运行 react-native init (项目名)sagaandprotobuf
创建成功后会看到如图1

图1

看到这个图说明已经创建成功了!

1.2 集成redux-saga

关于 redux 和 redux-saga 的介绍和功能就不详细说明了,网上很多

1.2.1 添加 redux 和 redux-saga

(1) 集成redux-saga 前我们先要集成redux。首先看一下当前生成的项目目录。如下图2


图2.png

(2)添加在package.json中添加rudux 相关包 , 运行 npm install 安装

"dependencies": {
    "react-redux": "^6.0.0",
    "redux": "^4.0.1",
    "redux-logger": "^3.0.6",
    "redux-saga": "^1.0.0"
  },

(3) 安装完成后 创建redux 和 redux-saga 的相关目录文件夹和文件:
   根目录下创建app文件夹,用来存放我们要写的代码。
   [1] 在app文件夹下新建三个文件夹:actions(用来存放以后所有的action),pages(用来存放页面),reducers(用来存放reducer),sagas(用来存放各模块的saga文件)。因为在项目开发中需要将各个模块的action 和 raducer分开开发,所以或有很多这类文件。
   [2] 在app文件夹下新建actionsTypes.js,rootReducers.js,rootsagas.js,store.js 文件文件。 在pages文件夹下新建 home.js。
   [3] 在action 文件夹里面新建 WebsocketAction.js,reducers 文件夹下新建 WebsocketReducer.js,sagas文件夹下新建 WebsocketSaga.js。
至此我们还没有写一行代码。只是将框架搭建起来。目录结构如下图3。


图3.png

(4)按照我上面的步骤建立好文件后,只需要跟着下面粘贴代码就可以了,开始复制粘贴:
[1] actionsTypes.js 代码如下

/**
 * create by sxf on 2019/2/14.
 * 功能: 事件类型统一分配
 */
export const CONNECTSUCCESS = 'CONNECTSUCCESS';
export const CONNECT = 'CONNECT';
export const CONNECTFALL = 'CONNECTFALL';
export const SENDMSG = 'SENDMSG';
export const CONNECTCLOSE = 'CONNECTCLOSE';
export const RETURNMSG = 'RETURNMSG';

[2] rootReducers.js 代码如下

import {combineReducers} from  'redux'
import WebsocketReducer from './reducers/WebsocketReducer'

//这里面必须要有初始数据 - 否则报错
const rootReducer = combineReducers({
    WebsocketReducer
});

export default rootReducer;

[3] store.js 代码如下:将redux-saga和redux结合起来


import {createStore, applyMiddleware, compose} from 'redux'
import createSagaMiddleware , { END } from 'redux-saga'
import {createLogger}  from 'redux-logger'
import rootReducer from './rootReducers'
import sagas from './rootsagas'

const configureStore = preloadedState => {
    const sagaMiddleware = createSagaMiddleware();
    const store = createStore(
        rootReducer,
        preloadedState,
        compose (
            applyMiddleware(sagaMiddleware, createLogger())
        )
    )
    sagaMiddleware.run(sagas);
    store.close = () => store.dispatch(END)
    return store;
}

const store = configureStore();
export default store;

[4] WebsocketAction.js 代码如下

import {CONNECT, CONNECTCLOSE, CONNECTFALL, CONNECTSUCCESS,SENDMSG,RETURNMSG } from './../actionsTypes'

const wsconnect =  (connectobj)  => ({ type : CONNECT,connectobj:connectobj});
const wsconnectclose =  ()  => ({ type : CONNECTCLOSE});
const connectsuccess =  ()  => ({ type : CONNECTSUCCESS});
const connectfall =  ()  => ({ type : CONNECTFALL});
const sendmsg =  (sendmsg)  => ({ type : SENDMSG,sendmsg:sendmsg});
const wsmsgres =  (msgstr)  => ({ type : RETURNMSG,msgstr:msgstr});


export {
    connectsuccess,
    connectfall,
    wsconnect,
    wsconnectclose,
    sendmsg,
    wsmsgres
}

[5] WebsocketReducer.js 代码如下

import * as types from './../actionsTypes'

const initwsState ={
    status:'未连接',
    isSuccess:false,
    ws:null,
    msg:""
}

export default function webSockerfun(state=initwsState,action) {
    switch (action.type){
        case types.CONNECTSUCCESS:
            return{
                ...state,
                status:"连接成功",
            }
            break;
        case types.CONNECTFALL:
            return{
                ...state,
                status:"未连接",
                msg:""
            }
            break;
        case types.RETURNMSG:
            return{
                ...state,
                msg:action.msgstr
            }
            break;
        default:
            return state;
    }
}

[6] 修改App.js 内容为下代码。使得默认页面指向 home.js,并将redux 和react-native结合起来。

import React, {Component} from 'react';
import { Provider } from 'react-redux'
import store from './app/store'
import Home from './app/pages/home'

type Props = {};
export default class App extends Component<Props> {
  render() {
    return (
        <Provider store={store}>
            <Home/>
        </Provider>
    );
  }
}

[7] 你可能发现怎么没写rootsagas 和 WebsocketSaga的代码呢?因为集成protobufjs是在WebsocketSaga当中。所以WebsocketSaga这部分要特别注意(重点和坑点 后面会讲)。现在的WebsocketSaga代码是一个无法运行的空的方法。
WebsocketSaga.js 代码如下

import { END} from 'redux-saga'
import { put , take, fork ,cancel ,cancelled,delay,call} from 'redux-saga/effects'
import {CONNECT,CONNECTCLOSE,SENDMSG}  from './../actionsTypes'
import {connectsuccess,connectfall,wsmsgres} from './../actions/WebsocketAction'
export function* watchWebsocket() {
     // 这里面未来会逻辑代码 现在增加这个方法主要是为了 rootsaga.js 不报错
}

rootsagas.js 代码如下

import {fork} from "redux-saga/effects";
import {watchWebsocket} from './sagas/WebsocketSaga'


export default function* rootSaga() {
    yield fork(watchWebsocket);
}

[7] home.js 页面的代码如下


import React, { Component } from 'react';
import {
    StyleSheet,
    Text,
    View,
    TouchableOpacity
} from 'react-native';
import { connect } from 'react-redux';
import {wsconnect,wsconnectclose,sendmsg} from './../actions/WebsocketAction'
import WebsocketReducer from "../reducers/WebsocketReducer";
class Home extends Component {
    _onConnect(){
        this.props.dispatch(wsconnect({mydispatch: this.props.dispatch}));
    }
    _onConnectclose(){
        this.props.dispatch(wsconnectclose());
    }
    _onsendmsg(){
        // 随便发送点数据
        this.props.dispatch(sendmsg({msgtext:"protoBuf发送数:"+ Math.round(Math.random()*100)}));
    }
    render() {
        return (
            <View style={styles.container}>
                <Text style={styles.counter}>{this.props.WebsocketReducer.status}</Text>
                <TouchableOpacity style={styles.reset} onPress={()=>this._onConnect()}>
                    <Text>连接websocket</Text>
                </TouchableOpacity>
                <TouchableOpacity style={styles.start} onPress={()=>this._onConnectclose()}>
                    <Text>断开连接</Text>
                </TouchableOpacity>
                <TouchableOpacity style={styles.stop} onPress={()=>this._onsendmsg()}>
                    <Text>发送消息</Text>
                </TouchableOpacity>
                <Text style={styles.counter}>{this.props.WebsocketReducer.msg}</Text>
            </View>
        );
    }
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        alignItems: 'center',
        justifyContent: 'center',
        flexDirection: 'column'
    },
    counter: {
        fontSize: 50,
        marginBottom: 70
    },
    reset: {
        margin: 10,
        backgroundColor: 'yellow'
    },
    start: {
        margin: 10,
        backgroundColor: 'yellow'
    },
    stop: {
        margin: 10,
        backgroundColor: 'yellow'
    }
})

const mapStateToProps = state => ({
    WebsocketReducer:state.WebsocketReducer
})

export default connect(mapStateToProps)(Home);

好了 截止到目前 redux-saga 和 redux 都集成成功了。

1.3 集成protobufjs

说明: 关于protobuf 的知识网上有很多 推荐看 点击前往这片文章。这个讲的比较好。不过这个里面有一个坑和官方git是一样的后面会说。

(1) 首先将 protobufjs 包下载下来, 添加包到package.json中 运行 npm install 安装

"dependencies": {
    ...
    "protobufjs": "^6.8.8"
  },

(2) 一般情况会和后端定义一个.proto文件,用来定义proto格式。这里我们在sagas文件夹下新建一个文件awesome.proto 这个文件的内容如下(这个文件的内容规范 请看官网或者是啊上面的推荐网站)

// awesome.proto
package awesomepackage;
syntax = "proto3";
message AwesomeMessage {
    string awesome_field = 1; // becomes awesomeField
}

(3) 将这个文件用pbjs 命令转成 json文件。(这个地方很坑,官网和其他地方都没有提到,如果不转的话在rudux-saga中是无法使用的。因为protobuf.load()方法采用的是回调函数的异步机制,违背了saga的书写规范。)
如何使用这个命令?
[1] 在\node_modules\protobufjs\bin目录下找到 pbjs 文件 这个就是命令文件。
[2] 控制台命令进入这个目录 cd node_modules\protobufjs\bin
[3] 运行 node ,这里面可能会运行不成功 卡在installing espree@^3.5.4 这个地方。我试了下 好像是npm 下载这个包会卡住, 这里手动 yarn add espree这个包吧(没有yarn?那你需要好好补补课了)

node  pbjs -t json E:\2019stude\react-native-reduxsaga-protobuf-websocket\mystudy\app\sagas\awesome.proto > E:\2019stude\react-native-reduxsaga-protobuf-websocket\mystudy\app\sagas\awesome.json

运行完以后再项目的sagas文件夹下可以看到awsome.json文件
[4] 回头将WebsocketSaga代码改成下面的

import { END} from 'redux-saga'
import { put , take, fork ,cancel ,cancelled,delay,call} from 'redux-saga/effects'
import {CONNECT,CONNECTCLOSE,SENDMSG}  from './../actionsTypes'
import {connectsuccess,connectfall,wsmsgres} from './../actions/WebsocketAction'
var protobuf = require("protobufjs");

var ws = null; // 缓存 websocket连接
var _mydispatch = null; // 这个变量是因为saga无法支持callback 只能变通处理(这也是个坑点)
var protpfile = null; // 缓存proto文件
export function* watchWebsocket() {
    while (true){
        const action = yield take(CONNECT);
        if(_mydispatch == null){
            _mydispatch = action.connectobj.mydispatch;
        }
        yield fork(connectWebsocket,_mydispatch);
        var sendmsgTask = yield fork(sendmsg);
        yield take(CONNECTCLOSE);
        yield fork(connectcolseWebsocket);
        yield cancel(sendmsgTask);
    }
}

function* sendmsg(){
    try{
        while (true){
            const sendaction = yield take(SENDMSG);
            yield fork(decodeencodewithproto,sendaction.sendmsg.msgtext);

        }
    }finally {
        if(yield cancelled()){
            console.log("取消了监听发送任务");
        }
    }
}

function* decodeencodewithproto(sendstr) {
    let restroot ;
    if(protpfile == null){
        // 缓存proto 对象
        restroot = yield call(protobuffun);
        protpfile = restroot;
    }else{
        restroot = protpfile;
    }
    var AwesomeMessage = restroot.lookupType("awesomepackage.AwesomeMessage");
    var payload = { awesomeField: sendstr };
    var errMsg = AwesomeMessage.verify(payload);
    if (errMsg)
        throw Error(errMsg);
    var message = AwesomeMessage.create(payload); // or use .fromObject if conversion is necessary
    var buffer = AwesomeMessage.encode(message).finish();
    ws.send(buffer);
}


function protobuffun() {
    return new Promise(resolve => {
        //之所以要转成json 就是因为这个地方无法使用reload方法 只能用require方法
        var jsonDescriptor = require("./awesome.json"); // exemplary for node
        var root = protobuf.Root.fromJSON(jsonDescriptor);
        resolve(root);
    })
}


function* connectcolseWebsocket() {
    ws.close();
}

function* connectWebsocket(mydispatch) {
    ws = new WebSocket("ws://echo.websocket.org");

    ws.onopen = () => {
        mydispatch(connectsuccess())
    };
    ws.onerror = e => {
        mydispatch(connectfall())
    };
    ws.onmessage = e => {
        console.log(e.data)
        var buf = new Uint8Array(e.data);
        var _AwesomeMessage = protpfile.lookupType("awesomepackage.AwesomeMessage");
        var message = _AwesomeMessage.decode(buf).awesomeField;
        console.log(message);
        mydispatch(wsmsgres(message))
    };
    ws.onclose = e => {
        // connection closed
        mydispatch(connectfall())
    };
}

OK 大工搞成了。跑起来看下效果吧!!


图4.png

项目的源代码在我的git上面 地址是 https://github.com/chen735623058/react-native-reduxsaga-protobuf-websocket。 如果帮到您了辛苦给颗星呗。

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

推荐阅读更多精彩内容