# React Native进阶

# 一、App导航框架设计

./src/index.js,作用是创建AppContainer并导出

import React from 'react';
import { createAppContainer, createSwitchNavigator } from 'react-navigation';
import { createBottomTabNavigator } from 'react-navigation-tabs';
import { createStackNavigator } from 'react-navigation-stack';
import Icon from 'react-native-vector-icons/FontAwesome';
import WelcomePage from './Welcome/index'; // 欢迎页
import HomeStackNavigator from './Home/index';
import CompanyStackNavigator from './Company/index';
import Message from './Message';
import My from './My';

// 底部tab导航,应用主体
const TabNavigator = createBottomTabNavigator(
  {
    Home: {
      screen: HomeStackNavigator,
      navigationOptions: {
        tabBarLabel: '职位',
      }
    },
    Company: {
      screen: CompanyStackNavigator,
      navigationOptions: {
        tabBarLabel: '公司',
      }
    },
    Message: {
      screen: Message,
      navigationOptions: {
        tabBarLabel: '消息',
      }
    },
    My: {
      screen: My,
      navigationOptions: {
        tabBarLabel: '我的',
      }
    }
  },
  {
    initialRouteName: 'Home',
    defaultNavigationOptions: ({ navigation }) => ({
      tabBarIcon: ({ focused, horizontal, tintColor }) => {
        const { routeName } = navigation.state;
        let iconName;
        if (routeName === 'Home') {
          iconName = 'globe';
        } else if (routeName === 'Company') {
          iconName = 'building-o';
        } else if (routeName === 'Message') {
          iconName = 'comments-o';
        } else if (routeName === 'My') {
          iconName = 'user-circle-o';
        }
        return <Icon name={iconName} size={20} color={tintColor} />;
      },
    }),
    tabBarOptions: {
      activeTintColor: 'rgb(29,216,200)',
      inactiveTintColor: 'gray',
    },
  },
);

// app欢迎页面,因为要切换到底部tab导航,所以要创建一个createStackNavigator
const AppInitNavigator = createStackNavigator({
  welcome: {
    screen: WelcomePage,
    navigationOptions: {
      header: null,
    }
  }
});

// 因为需要从欢迎页面切换到底部tab导航并且只有1次,所以用createSwitchNavigator
const switchNavigator = createSwitchNavigator({
  Init: AppInitNavigator,
  Main: TabNavigator,
}, {
  initialRouteName: 'Init',
});

const AppContainer = createAppContainer(switchNavigator);
export default AppContainer;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84

# 二、Redux与React Navigation结合集成

App.js

import React, { Component } from 'react';
import { StyleSheet, StatusBar } from 'react-native';
import {
  createReduxContainer,
  createReactNavigationReduxMiddleware
} from 'react-navigation-redux-helpers';
import { createStore, applyMiddleware } from 'redux';
import { Provider, connect } from 'react-redux';
import AppContainter from './src/index';
import appReducer from './src/reducer';

const middleware = createReactNavigationReduxMiddleware(state => state.nav);
const AppReduxContainer = createReduxContainer(AppContainter);

const mapStateToProps = state => ({
  state: state.nav,
});
const AppWithNavigationState = connect(mapStateToProps)(AppReduxContainer);

const store = createStore(appReducer, applyMiddleware(middleware));

class App extends Component {
  render() {
    return (
      <Provider store={store} style={styles.container}>
        <StatusBar barStyle="light-content" />
        <AppWithNavigationState />
      </Provider>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
});

export default App;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

./src/reducer.js,作用是combineReducers,把reducer结合起来

import { combineReducers } from 'redux';
import { createNavigationReducer } from 'react-navigation-redux-helpers';
import AppContainter from './index';
import companyList from './Company/reducer';

const navReducer = createNavigationReducer(AppContainter);

export default combineReducers({
  nav: navReducer,
  companyList
});

1
2
3
4
5
6
7
8
9
10
11
12

# 三、网络编程

React Native 提供了和 web 标准⼀致的Fetch API,⽤于满⾜开发者访问⽹络的需求。

fetch('https://mywebsite.com/mydata.json');

// 提交数据
fetch('https://mywebsite.com/endpoint/', {
  method: 'POST',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    firstParam: 'yourValue',
    secondParam: 'yourOtherValue',
  }),
});

// 普通表单请求
fetch('https://mywebsite.com/endpoint/', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded',
  },
  body: 'key1=value1&key2=value2',
});

// 返回promise
function getMoviesFromApiAsync() {
  return fetch('https://facebook.github.io/react-native/movies.json')
    .then((response) => response.json())
    .then((responseJson) => {
      return responseJson.movies;
    })
    .catch((error) => {
      console.error(error);
    });
}

// async/await语法
async function getMoviesFromApi() {
  try {
    // 注意这;里的await语句,其所在的函数必须有async关键字声明
    let response = await fetch(
      'https://facebook.github.io/react-native/movies.json',
    );
    let responseJson = await response.json();
    return responseJson.movies;
  } catch (error) {
    console.error(error);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

# 错误处理

当接收到⼀个代表错误的HTTP状态码时,从fetch()返回的promise不会被标记为reject,即使该HTTP响应的状态码是404或500。相反,它会将Promise状态标记为resolve(但是会将resolve的返回值的ok属性设置为false),仅当⽹络故障时或请求被阻⽌时,才会被标记为reject。⼀次请求没有调⽤reject并不代表请求⼀定成功了,通常需要在resolve情况下,再判断response.ok属性为true.

let url = `https://api.github.com/search/repositories?q=NodeJS`;
fetch(url)
  .then(response => {
    if (response.ok) {
      return response.text();
    }
    throw new Error("Network response was not ok");
  })
  .then(responseText => {
    console.log(responseText);
  })
  .catch(e => {
    console.log(e.toString());
  });
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 四、数据存储AsyncStorage

AsyncStorage 是⼀个简单的、异步的、持久化的 Key-Value 存储系统,它对于 App 来说是全局性的。可⽤来代替 LocalStorage。推荐在AsyncStorage的基础上做⼀层抽象封装,⽽不是直接使⽤ AsyncStorage。

在iOS上,AsyncStorage 在原⽣端的实现是把较⼩值存放在序列化的字典中,而把较大值写⼊单独的⽂件。在Android上,AsyncStorage 会尝试使⽤RocksDB,或选择 SQLite。

安装: yarn add @react-native-community/async-storage

# 基本使用

import AsyncStorage from '@react-native-community/async-storage';

// 存数据
async doSave(){
  //⽤法1
  AsyncStorage.setItem(Key, Value, err => {
    err && console.log(err.toString())
  })
  //⽤法2
  AsyncStorage.setItem(Key, Value)
    .catch(e => {
      err && console.log(err.toString())
    })
  //⽤法3
  try {
    await AsyncStorage.setItem(Key, value)
  } catch (err) {
    err && console.log(err.toString())
  }
}

// 读取数据
async getData(){
  //⽤法1
  AsyncStorage.getItem(Key, (err, value) => {
    console.log(value)
    err && console.log(err.toString())
  })
  //⽤法2
  AsyncStorage.getItem(Key)
    .then(value => {
      console.log(value)
    })
    .catch(e => {
      err && console.log(err.toString())
    })
  //⽤法3
  try {
    const value = await AsyncStorage.getItem(Key)
    console.log(value)
  } catch (err) {
    err && console.log(err.toString())
  }
}

// 删除数据
async doRemove(){
  //⽤法1
  AsyncStorage.removeItem(Key, (err) => {
    err && console.log(err.toString())
  })
  //⽤法2
  AsyncStorage.removeItem(Key)
    .catch(e => {
      err && console.log(err.toString())
    })
  //⽤法3
  try {
    await AsyncStorage.removeItem(Key)
  } catch (err) {
    err && console.log(err.toString())
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

# 五、离线缓存框架设计

离线缓存有什么好处?

  • 提升用户体验,⽤户的⽹络情况我们不能控制,但是我们可以离线存储提升体验。
  • 节省流量:节省服务器流量,节省⽤户⼿机的流量

离线缓存有什么限制?数据的实时性要求不高,推荐使用

离线缓存的策略:

  • 优先从本地获取数据,如果数据过时或者不存在,则从服务器获取数据,数据返回后同时将数据同步到本地数据库。
  • 优先从服务器获取数据,数据返回后同步到本地数据库,如果发⽣⽹络故障,才从本地获取数据。

离线缓存框架的设计:按照第⼀个策略,如果数据过时或者不存在,则从服务器获取数据,数据返回后同时将数据同步到本地数据库。

  • 优先从本地获取数据
  • 如果数据存在且在有效期内,我们将数据返回
  • 否则获取⽹络数据

封装DataStore,外部调用使用fetchData方法即可

import AsyncStorage from "@react-native-community/async-storage";

export default class DataStore {
  // 检查有效期
  static checkTimestampValid(timestamp) {
    const currentDate = new Date();
    const targetDate = new Date();
    targetDate.setTime(timestamp);
    if (currentDate.getMonth() !== targetDate.getMonth()) return false;
    if (currentDate.getDate() !== targetDate.getDate()) return false;
    if (currentDate.getHours() - targetDate.getHours() > 4) return false; //有效期4个⼩时
    return true;
  }

  // 外部调用
  fetchData(url) {
    return new Promise((resolve, reject) => {
      //获取本地数据
      this.fetchLocalData(url)
        .then(wrapdata => {
          //检查有效期
          if (wrapdata && DataStore.checkTimestampValid(wrapdata.timestamp)) {
            resolve(wrapdata);
          } else {
            //获取⽹络数据
            this.fetchNetData(url)
              .then(data => {
                //给数据打个时间戳
                resolve(this._wrapData(data));
              })
              .catch(e => {
                reject(e);
              });
          }
        })
        .catch(error => {
          this.fetchNetData(url)
            .then(data => {
              resolve(this._wrapData(data));
            })
            .catch(error => {
              reject(error);
            });
        });
    });
  }
  
  // 保存数据到本地
  saveData(url, data, callback) {
    if (!data || !url) return;
    AsyncStorage.setItem(url, JSON.stringify(this._wrapData(data)), callback);
  }
  
  // 给离线的数据添加⼀个时间戳,便于计算有效期
  _wrapData(data) {
    return { data: data, timestamp: new Date().getTime() }; //本地时间,推荐服务器时间
  }

  // 获取本地数据
  fetchLocalData(url) {
    return new Promise((resolve, reject) => {
      AsyncStorage.getItem(url, (err, result) => {
        if (!err) {
          resolve(JSON.parse(result)); // getItem获取到的是string,我们需要将其反序列化为object
        } else {
          reject(err);
          console.log(err);
        }
      });
    });
  }

  // 获取网络数据
  fetchNetData(url) {
    return new Promise((resolve, reject) => {
      fetch(url)
        .then(response => {
          if (response.ok) {
            return response.json();
          }
          throw new Error("network response was not ok");
        })
        .then(responseData => {
          // 保存数据到本地
          this.saveData(url, responseData);
          resolve(responseData);
        })
        .catch(e => {
          reject(e);
        });
    });
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
Last Updated: 10/4/2020, 3:36:03 PM