React-Native APP前端开发框架从零搭建

web app前端开发框架|android开发前端框架|自适应 前端开发框架

一、须知

全文技术栈
核心库:React-Native@0.54.0
路由导航:React-Native-Navigation
状态管理:Redux、Redux-Thunk、Redux-Saga、Redux-persist
静态测试:Flow

本文适合有对React家族有一定使用经验,但对从零配置一个App不是很熟悉,又想要从零体验一把搭建app的感觉

我自己就是这种情况,中途参与到项目中,一直没有掌控全局的感觉,所以这次趁着项目重构的机会,自己也跟着从零配置了一遍,并记录了下来,希望能跟同学们一起学习,如果有说错的地方,也希望大家指出来,或者有更好的改进方式,欢迎交流。

如果有时间的同学,跟着亲手做一遍是最好的,对于如何搭建一个真实项目比较有帮助。整个项目已经上传到github,懒的动手的同学可以直接clone下来跟着看,欢迎一起完善,目前的初步想法是对一部分的同学有所帮助,后面有时间的话,可能会完善成一个比较健壮的RN基础框架,可以直接clone就开发项目那种本项目github仓库地址

这里对每个库或者内容只做配置介绍,不做基础用法讲解;

环境:mac,xcode
window的同学也可以看,不过需要自己搞好模拟器开发环境

二、快速建立一个RN APP

Getting Started · React Native
如果RN的基础配置环境没有配置好,请点击上方链接到官网进行配置

react-native init ReactNativeNavigationDemo
cd ReactNativeNavigationDemo
react-native run-ios

因为一开始就计划好了用React-Native-Navigation作为导航库,所以名字起得长了点,大家起个自己喜欢的吧

成功后会看到这个界面

三、React-Native-Navigation

1、安装与iOS配置

React Native Navigation – truly native navigation for iOS and Android
英文好的同学看着官方文档配就可以了,实在看不懂的可以对照着我下面的图看。
iOS的需要用到xcode,没做过的可能会觉得有点复杂,所以我跑了一遍流程并截图出来了
至于android的配置,文档写的很清晰,就不跑了。

1、安装最新版本
yarn add react-native-navigation@latest

2、添加xcode工程文件

图中的路径文件是指./node_modules/react-native-navigation/ios/ReactNativeNavigation.xcodeproj
3、把上面添加的工程文件添加到库中

4、添加路径

$(SRCROOT)/../node_modules/react-native-navigation/ios
记得图中第5点设置为recursive

5、修改ios/[app name]/AppDelegate.m文件

把整个文件内容替换成下面代码

import “AppDelegate.h”
import <React/RCTBundleURLProvider.h>
import <React/RCTRootView.h>
// 添加这一句
import “RCCManager.h”
@implementation AppDelegate
– (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSURL*jsCodeLocation;
// 把原来的内容都注释掉,换成下面代码
ifdef DEBUG
jsCodeLocation = [[RCTBundleURLProvidersharedSettings] jsBundleURLForBundleRoot:@”index” fallbackResource:nil];
else
jsCodeLocation = [[NSBundlemainBundle] URLForResource:@”main” withExtension:@”jsbundle”];
endif
self.window = [[UIWindowalloc] initWithFrame:[UIScreenmainScreen].bounds];
self.window.backgroundColor = [UIColorwhiteColor];
[[RCCManager sharedInstance] initBridgeWithBundleURL:jsCodeLocation launchOptions:launchOptions];
returnYES;
}

2、基础使用

1、先新建几个页面

cd src
mkdir home mine popularize
touch home/index.js mine/index.js popularize/index.js

每个index.js文件里面都是一样的结构,非常简单

import React, { Component } from ‘react’
import { Text, View } from ‘react-native’
type Props = {}
export default class MineHome extends Component<Props> {
    render() {
        return (
            <View>
                <Text>MineHome</Text>
            </View>
        )
    }
}

2、src/index.js 注册所有的页面,统一管理

import { Navigation } from ‘react-native-navigation’
import Home from ‘./home/index’
import PopularizeHome from ‘./popularize/index’
import MineHome from ‘./mine/index’
// 注册所有的页面
export function registerScreens() {
    Navigation.registerComponent(‘home’, () => home)
    Navigation.registerComponent(‘popularize’, () => popularize)
    Navigation.registerComponent(‘mine’, () => mine)
}

在这里先插一句,如果要引入Redux的话,就在这里直接传入storeProvider

export function registerScreens(store,Provider) {
Navigation.registerComponent(‘home’,() =>PageOne,store,Provider)
}

3、App.js 修改app的启动方式,并稍微修改一下页面样式

import { Navigation } from ‘react-native-navigation’
import { registerScreens } from ‘./src/screen/index’
// 执行注册页面方法
registerScreens()
// 启动app
Navigation.startTabBasedApp({
    tabs: [
        {
            label: ‘home’,
            screen: ‘home’,
            title: ‘首页’,
            icon: require(‘./src/assets/home.png’),
        },
        {
            screen: ‘popularize’,
            title: ‘推广’,
            icon: require(‘./src/assets/add.png’),
            iconInsets: {
                top: 5,
                left: 0,
                bottom: -5,
                right: 0,
            },
        },
        {
            label: ‘mine’,
            screen: ‘mine’,
            title: ‘我’,
            icon: require(‘./src/assets/mine.png’),
        },
    ],
    appStyle: {
        navBarBackgroundColor: ‘#263136’, //顶部导航栏背景颜色
        navBarTextColor: ‘white’, //顶部导航栏字体颜色
    },
    tabsStyle: {
        tabBarButtonColor: ‘#ccc’, //底部按钮颜色
        tabBarSelectedButtonColor: ‘#08cb6a’, //底部按钮选择状态颜色
        tabBarBackgroundColor: ‘#E6E6E6’, //顶部条背景颜色
    },
})

3、页面跳转和传递参数

新建一个NextPage页面,记得到src/screen/index.js里面注册该页面

Navigation.registerComponent(‘nextPage’, () => NextPage, store, Provider);
touch src/screen/home/nextPage.js

import React, { Component } from ‘react’
import { Text, View, StyleSheet, TouchableOpacity } from ‘react-native’
import * as homeActions from ‘../../action/home’
type Props = {}
class NextPage extends Component<Props> {
    // 自定义tabbar样式,隐藏底部导航
    static navigatorStyle = {
        tabBarHidden: true,
    }
    componentDidMount() {
        // 打印数据,可以找到到上一个页面传递过来的数据
        console.log(‘props’, this.props)
    }
    render() {
        return (
            <View style={styles.container}>
                <Text>NextPage</Text>
            </View>
        )
    }
}
const styles = StyleSheet.create({
    container: {
        backgroundColor: ‘#ccc’,
        flex: 1,
        justifyContent: ‘center’,
        alignItems: ‘center’,
    },
})
export default NextPage

然后在src/screen/home/index.js文件里面加一个跳转按钮,并传递一个props数据

;<TouchableOpacity
    style={styles.jumpBtn}
    onPress={() => {
        this.props.navigator.push({
            screen: ‘nextPage’,
            title: ‘下一页’,
            passProps: {
                passData: ‘hh’,
            },
        })
    }}
>
    <Text style={styles.btnText}>跳转到NextPage</Text>
</TouchableOpacity>

四、状态管理:Redux

没有相关概念的小伙伴请先看看官方文档,下面直接讲目录结构配置
自述 · GitBook

1、安装

yarn add redux react-redux

2、目录构建

一般来说,有以下两种目录构建方式
一是把同一个页面的action和reducer写在同一个文件夹下面(可以称之为组件化)

二是把所有的action放在一个文件夹,所有的reducer放在一个文件夹,统一管理

这两种方式各有好坏,不在此探究,这里我用第二种

一通操作猛如虎

cd src
mkdir action reducer store
touch action/index.js reducer/index.js store/index.js
touch action/home.js action/mine.js action/popularize.js
touch reducer/home.js reducer/mine.js reducer/popularize.js

以上命令敲完后,目录结构应该长下面这样,每个页面都分别拥有自己的action和reducer文件,但都由index.js文件集中管理输出

3、创建store、action、reducer

关于创这三块内容的先后顺序,理论上来说,应该是先有store,然后有reducer,再到action
但写的多了之后,就比较随心了,那个顺手就先写哪个。
按照我自己的习惯,我喜欢从无写到有,比如说
store里面要引入合并后的reducer,那我就会先去把reducer给写了
import combinedReducer from '../reducer';
但写reducer之前,好像又需要先引入action,所以我由可能跑去先写action

这里不讨论正确的书写顺序,我就暂且按照自己的习惯来写吧

action
我喜欢集中管理的模式,所以所有的antion我都会集中起来
index.js文件作为总的输出口
这里定义了所有的action-type常量

// home页面
export const HOME_ADD = ‘HOME_ADD’;
export const HOME_CUT = ‘HOME_CUT’;
// mine页面
export const MINE_ADD = ‘MINE_ADD’;
export const MINE_CUT = ‘MINE_CUT’;
// popularize页面
export const POPULARIZE_ADD = ‘POPULARIZE_ADD’;
export const POPULARIZE_CUT = ‘POPULARIZE_CUT’;

然后去写其他各自页面的action.js文件,这里只以home页面作为例子,其他页面就不写了,打开action/home.js文件

import * as actionTypes from ‘./index’
export function homeAdd(num) {
    return {
        type: actionTypes.HOME_ADD,
        num,
    }
}
export function homeCut(num) {
    return {
        type: actionTypes.HOME_CUT,
        num,
    }
}

reducer
先写一个home页面的reducer,打开reducer/home.js文件
其他页面也同理

import * as actionTypes from ‘../action/index’;
const initState = {
initCount:0,
name:”,
age:”,
job:”
}
export default function count(state = initState, action) {
switch (action.type) {
caseactionTypes.HOME_ADD:
return {
…state,
…action.initCount:
}
caseactionTypes.HOME_CUT:
return {
…state,
…action.initCount
}
default:
returnstate;
}
}

然后把所有子reducer页面合并到index.js文件进行集中输出

import homeReducer from ‘./home’
import popularizeReducer from ‘./popularize’
import mineReducer from ‘./mine’
const combineReducers = {
    home: homeReducer,
    popularize: popularizeReducer,
    mine: mineReducer,
}
export default combineReducers

还可以引入combineReducers方法,和自己写也是一样的

import { combineReducers } from ‘redux’
// 引入子文件
export default combineReducers({
    home: homeReducer,
    popularize: popularizeReducer,
    mine: mineReducer,
})

创建store

创建好reducer之后,打开store/index.js文件

import { createStore } from ‘redux’
import combineReducers from ‘../reducer/index’
const store = createStore(combineReducers)
export default store

就这么简单,store就创建好了

4、store注入

使用过redux的同学都知道,react-redux上场了,它提供了Providerconnect方法

前面有提到react-native-navigation注入redux的方式,其实差不多
但需要每个子页面都注入store、Provider
src/index.js修改如下

import { Navigation } from ‘react-native-navigation’
import Home from ‘./home/index’
import PopularizeHome from ‘./popularize/index’
import MineHome from ‘./mine/index’
// 注册所有的页面
export function registerScreens(store, Provider) {
    Navigation.registerComponent(‘home’, () => Home, store, Provider)
    Navigation.registerComponent(
        ‘popularize’,
        () => PopularizeHome,
        store,
        Provider,
    )
    Navigation.registerComponent(‘mine’, () => MineHome, store, Provider)
}

App.js修改执行页面注册的方法即可

import { Provider } from ‘react-redux’
import store from ‘./src/store/index’
// 执行注册页面方法
registerScreens(store, Provider)

5、初步体验redux

现在来体验一下redux,打开src/screen/home/index.js文件

import两个方法

import { connect } from ‘react-redux’
import { bindActionCreators } from ‘redux’

导入action

import * as homeActions from ‘../../action/home’;

定义两个方法,并connect起来

function mapStateToProps(state) {
    return {
        home: state.home,
    }
}
function mapDispatchToProps(dispatch) {
    return {
        homeActions: bindActionCreators(homeActions, dispatch),
    }
}
export default connect(
    mapStateToProps,
    mapDispatchToProps,
)(Home)

现在在页面上打印initCount来看一下,核心代码

<Text>initCount: {this.props.home.initCount}</Text>

src/screen/home/index.js完整代码如下

import React, { Component } from ‘react’
import { Text, View, StyleSheet } from ‘react-native’
import { connect } from ‘react-redux’
import { bindActionCreators } from ‘redux’
import * as homeActions from ‘../../action/home’
type Props = {}
class Home extends Component<Props> {
    render() {
        return (
            <View style={styles.container}>
                <Text>Home</Text>
                <Text>initCount: {this.props.home.initCount}</Text>
            </View>
        )
    }
}
const styles = StyleSheet.create({
    container: {
        backgroundColor: ‘#ccc’,
        flex: 1,
        justifyContent: ‘center’,
        alignItems: ‘center’,
    },
})
function mapStateToProps(state) {
    return {
        home: state.home,
    }
}
function mapDispatchToProps(dispatch) {
    return {
        homeActions: bindActionCreators(homeActions, dispatch),
    }
}
export default connect(
    mapStateToProps,
    mapDispatchToProps,
)(Home)

从页面可以看到,已经读到状态树里面的数据了

让我们再来试一下action的加法和减法
src/screen/home/index.js完整代码如下

import React, { Component } from ‘react’
import { Text, View, StyleSheet, TouchableOpacity } from ‘react-native’
import { connect } from ‘react-redux’
import { bindActionCreators } from ‘redux’
import * as homeActions from ‘../../action/home’
type Props = {}
class Home extends Component<Props> {
    render() {
        return (
            <View style={styles.container}>
                <Text>Home</Text>
                <Text>initCount: {this.props.home.initCount}</Text>
                <TouchableOpacity
                    style={styles.addBtn}
                    onPress={() => {
                        this.props.homeActions.homeAdd({
                            initCount: this.props.home.initCount + 2,
                        })
                    }}
                >
                    <Text style={styles.btnText}>加2</Text>
                </TouchableOpacity>
                <TouchableOpacity
                    style={styles.cutBtn}
                    onPress={() => {
                        this.props.homeActions.homeCut({
                            initCount: this.props.home.initCount – 2,
                        })
                    }}
                >
                    <Text style={styles.btnText}>减2</Text>
                </TouchableOpacity>
            </View>
        )
    }
}
const styles = StyleSheet.create({
    container: {
        backgroundColor: ‘#ccc’,
        flex: 1,
        justifyContent: ‘center’,
        alignItems: ‘center’,
    },
    addBtn: {
        backgroundColor: ‘green’,
        marginVertical: 20,
        width: 200,
        height: 59,
        justifyContent: ‘center’,
        alignItems: ‘center’,
        borderRadius: 10,
    },
    cutBtn: {
        backgroundColor: ‘red’,
        width: 200,
        height: 59,
        justifyContent: ‘center’,
        alignItems: ‘center’,
        borderRadius: 10,
    },
    btnText: {
        fontSize: 18,
        color: ‘white’,
    },
})
function mapStateToProps(state) {
    return {
        home: state.home,
    }
}
function mapDispatchToProps(dispatch) {
    return {
        homeActions: bindActionCreators(homeActions, dispatch),
    }
}
export default connect(
    mapStateToProps,
    mapDispatchToProps,
)(Home)

现在再来验证下一个东西,这个页面改完store里面的状态后,另一个页面会不会同步
src/mine/index.js文件修改如下

import React, { Component } from ‘react’
import { Text, View } from ‘react-native’
import { connect } from ‘react-redux’
import { bindActionCreators } from ‘redux’
import * as homeActions from ‘../../action/home’
type Props = {}
class MineHome extends Component<Props> {
    render() {
        return (
            <View>
                <Text>initCount: {this.props.home.initCount}</Text>
            </View>
        )
    }
}
function mapStateToProps(state) {
    return {
        home: state.home,
    }
}
function mapDispatchToProps(dispatch) {
    return {
        homeActions: bindActionCreators(homeActions, dispatch),
    }
}
export default connect(
    mapStateToProps,
    mapDispatchToProps,
)(MineHome)

在该页面上读取同一个数据this.props.home.initCount,然后在第一个页面上加减数据,再看该页面,会发现initCount也同步变化
也就是说明:我们已经在进行状态管理了

五、状态跟踪redux-logger

这个时候,我们会发现,虽然状态共享了,但目前还没有办法跟踪状态,以及每一步操作带来的状态变化。

但总不能每次都手动打印状态到控制台里面吧?

这时候,上面装的redux-logger就上场了

具体使用看下官方文档,很简单,直接上代码吧
GitHub – evgenyrodionov/redux-logger: Logger for Redux

安装(之前已经安装过了,没装的可以再装一下)
yarn add redux-logger

使用
它作为一个中间件,中间件的用法请回redux官网查阅
store/index.js文件修改如下

import { createStore, applyMiddleware } from ‘redux’
import combineReducers from ‘../reducer/index’
import logger from ‘redux-logger’
const store = createStore(combineReducers, applyMiddleware(logger))
export default store

接下来每次派发action,控制台都会自动打印出来,是不是省心省事?

六、异步管理:redux-thunk

redux-thunk是什么请移步Redux异步控制
出发点:需要组件对同步或异步的 action 无感,调用异步 action 时不需要显式地传入 dispatch

通过使用指定的 middleware,action 创建函数除了返回 action 对象外还可以返回函数。这时,这个 action 创建函数就成为了 thunk

当 action 创建函数返回函数时,这个函数会被 Redux Thunk middleware 执行。这个函数并不需要保持纯净;它还可以带有副作用,包括执行异步 API 请求。这个函数还可以 dispatch action,就像 dispatch 前面定义的同步 action 一样
thunk 的一个优点是它的结果可以再次被 dispatch

1、安装

yarn add redux-thunk

2、注入store

作为一个中间件,它的使用方式和上面logger一样,直接引入即可

import thunk from ‘redux-thunk’;
middleware.push(thunk);

3、使用方式

action/home.js文件修改如下

import post from ‘../utils/fetch’
export function getSomeData() {
    return (dispatch) => {
        post(‘/get/data’, {}, (res) => {
            const someData = res.data.someData
            dispatch({
                type: actionTypes.HOME_GET_SOMEDATA,
                someData,
            })
        })
    }
}

action/index.js总入口记得加上对应的action
export const HOME_GET_SOMEDATA = 'HOME_GET_SOMEDATA';

题外话:封装请求函数post

此处稍微插入一句,关于封装请求函数post(以下是精简版,只保留了核心思想)

cd src
mkdir utils
touch utils/fetch.js

公用的方法和函数都封装在utils文件夹中
utils/fetch.js文件如下

export default function post(url, data, sucCB, errCB) {
    // 域名、body、header等根据各自项目配置,还有部分安全,加密方面的设置
    const host = ‘www.host.com’
    const requestUrl = `${host}/${url}`
    const body = {}
    const headers = {
        ‘Content-Type’: ‘application/json’,
        ‘User-Agent’: ”,
    }
    // 用的是fetch函数
    fetch(requestUrl, {
        method: ‘POST’,
        headers: headers,
        body: body,
    })
        .then((res) => {
            if (res && res.status === 200) {
                return res.json()
            } else {
                throw new Error(‘server’)
            }
        })
        .then((res) => {
            // 精简版判断
            if (res && res.code === 200 && res.enmsg === ‘ok’) {
                // 成功后的回调
                sucCB(res)
            } else {
                // 失败后的回调
                errCB(res)
            }
        })
        .catch((err) => {
            // 处理错误
        })
}

八、异步管理:Redux-Saga

基本概念请移步自述 | Redux-saga 中文文档

出发点:需要声明式地来表述复杂异步数据流(如长流程表单,请求失败后重试等),命令式的 thunk 对于复杂异步数据流的表现力有限

1、安装

yarn add redux-saga

2、创建saga文件

创建顺序有点像reducer
我们先创建saga相关文件夹和文件,最后再来注入store里面

cd src
mkdir saga
touch saga/index.js saga/home.js saga/popularize.js saga/mine.js

先修改saga/home.js文件

import { put, call, takeLatest } from ‘redux-saga/effects’
import * as actionTypes from ‘../action/index’
import * as homeActions from ‘../action/home’
import * as mineActions from ‘../action/mine’
import post from ‘../utils/fetch’
function getSomeThing() {
    post(‘/someData’, {}, (res) => {}, (err) => {})
}
function* getUserInfo({ sucCb, errCB }) {
    try {
        const res = yield call(getSomeThing())
        const data = res.data
        yield put(homeActions.getSomeData())
        yield put(homeActions.setSomeData(data))
        yield call(sucCb)
    } catch (err) {
        yield call(errCB, err)
    }
}
export const homeSagas = [
    takeLatest(actionTypes.HOME_GET_SOMEDATA, getUserInfo),
]

saga/mine.js文件
export const mineSagas = []
saga/popularize.js文件
export const popularizeSagas = []

saga/index.js文件作为总输出口,修改如下

import { all } from ‘redux-saga/effects’
import { homeSagas } from ‘./home’
import { mineSagas } from ‘./mine’
import { popularizeSagas } from ‘./popularize’
export default function* rootSaga() {
    yield all([…homeSagas, …mineSagas, …popularizeSagas])
}

3、把saga注入store

store/index.js文件修改

import createSagaMiddleware from ‘redux-saga’;
import rootSaga from ‘../saga/index’;
// 生成saga中间件
const sagaMiddleware = createSagaMiddleware(rootSaga);
middleware.push(sagaMiddleware);

九、数据持久化:redux-persist

GitHub – rt2zz/redux-persist: persist and rehydrate a redux store
顾名思义,数据持久化,一般用来保存登录信息等需要保存在本地的数据;
因为store中的数据,在每次重新打开app后,都会回复到reducer中的initState的初始状态,所以像登录信息这种数据就需要持久化的存储了。
RN自带的AsyncStorage可以实现这个功能,但使用起来比较繁琐,而且没有注入到store中去,没办法实现统一状态管理,所以redux-persist就出场了

安装
yarn add redux-persist

注入store
store/index.js文件完整代码如下

import { createStore, applyMiddleware } from ‘redux’
import logger from ‘redux-logger’
import thunk from ‘redux-thunk’
import createSagaMiddleware from ‘redux-saga’
import { persistStore, persistCombineReducers } from ‘redux-persist’
import storage from ‘redux-persist/es/storage’
import combineReducers from ‘../reducer/index’
import rootSaga from ‘../saga/index’
const persistConfig = {
    key: ‘root’,
    storage,
    // 只有mine的数据会被persist
    whitelist: [‘mine’],
}
// 对reducer数据进行persist配置
const persistReducer = persistCombineReducers(persistConfig, combineReducers)
const sagaMiddleware = createSagaMiddleware()
// 中间件
const createStoreWithMiddleware = applyMiddleware(
    thunk,
    sagaMiddleware,
    logger,
)(createStore)
const configuerStore = (onComplete) => {
    let store = createStoreWithMiddleware(persistReducer)
    let persistor = persistStore(store, null, onComplete)
    sagaMiddleware.run(rootSaga)
    return { persistor, store }
}
export default configuerStore

到目前为止,我们已经引入了redux-logger、redux-thunk、redux-saga、redux-persist,核心开发代码库已经配置完毕了

React Native填坑之旅–Flow篇(番外)

九、静态测试:Flow

待更新,这有一篇链接可以先看
React Native填坑之旅—Flow篇(番外) – full stack dev stills – SegmentFault 思否

后话

到目前为止,我们已经引入了redux-logger、redux-thunk、redux-saga、redux-persist

核心开发代码库已经配置完毕了

接下来还有一些可以作为开发时的辅助性配置,比如Flow 、Babel(RN初始化时已经配好了)、Eslint等等

另外,既然是App,那最终目的当然就是要上架App Store和各大安卓市场,后面可能还会分享一下关于极光推送jPush、热更新CodePush、打包上传审核等方面的内容。

react 后台前端开发|前端开发 react 笔试题|前端开发框架react

» 本文来自:前端开发者 » 《React-Native APP前端开发框架从零搭建》
» 本文链接地址:https://www.rokub.com/5099.html
» 您也可以订阅本站:https://www.rokub.com
赞(0)
64K

评论 抢沙发

评论前必须登录!