乱花渐欲迷人眼,返璞归真F8(2)


title: '乱花渐欲迷人眼,返璞归真F8(2)'
date: 2017-04-06 10:45:08
categories: F8App源码阅读
tags: React-native


入口和配置文件

./F8APP/js/setup.js文件

这一部分我们从index.ios.js文件顺藤摸瓜找到了


setup.js文件
./F8APP/js/setup.js

 'use strict';

var F8App = require('F8App');//主程序文件的入口
var FacebookSDK = require('FacebookSDK');//处理facebook登录和好友的API
var Parse = require('parse/react-native');//parse的客户端
var React = require('React');
var Relay = require('react-relay');//react程序的组件也可以使用Relay的数据层,这个在info这个组件中使用

var { Provider } = require('react-redux');//Redux的包装器
var configureStore = require('./store/configureStore');//store

var {serverURL} = require('./env');//环境配置,是parsesever的配置地址

function setup(): React.Component {
  console.disableYellowBox = true;
  //parseServer后面会结合leancloud来看看代码,两者的API是一样的
  Parse.initialize('oss-f8-app-2016');//初始化一个parse
  Parse.serverURL = `${serverURL}/parse`;//parse的url地址

  FacebookSDK.init();//初始化Facebook的配置
  Parse.FacebookUtils.init();
  Relay.injectNetworkLayer(//Realy数据层的配置
    new Relay.DefaultNetworkLayer(`${serverURL}/graphql`, {
      fetchTimeout: 30000,
      retryDelays: [5000, 10000],
    })
  );

  class Root extends React.Component {
    constructor() {
      super();
      this.state = {//初始化state
        isLoading: true,
        store: configureStore(() => this.setState({isLoading: false})),
      };
    }
    render() {
      if (this.state.isLoading) {
        return null;
      }
      return (//注入store给ui组件使用
        <Provider store={this.state.store}>
          <F8App />
        </Provider>
      );
    }
  }

  return Root;
}

global.LOG = (...args) => {
  console.log('/------------------------------\\');
  console.log(...args);
  console.log('\\------------------------------/');
  return args[args.length - 1];
};

module.exports = setup;

这个文件在去年阅读的时候直接忽略了.最近研究了parseServer的本地部署和相关的graphql的使用以及leancloud的使用,才发现这个部分真的是非常便利.只要是有了数据对象最好还能有schema,model.后台基本都不需要了.当然不可能是完全替代服务器的所有功能.这个到了相应的地方再说.

接下来是F8APP的入口文件

./F8APP/js/F8App.js

./F8APP/js/F8App.js

 
'use strict';

var React = require('React');
var AppState = require('AppState');
var LoginScreen = require('./login/LoginScreen');//登录组件
var PushNotificationsController = require('./PushNotificationsController');//推送组件
var StyleSheet = require('StyleSheet');
var F8Navigator = require('F8Navigator');//导航组件
var CodePush = require('react-native-code-push');//热更新组件
var View = require('View');
var StatusBar = require('StatusBar');//状态栏组件
var {//这里的每一action最终都会形成state这棵树下的次级分枝
  //名字都非常的醒目和直接,我们可以直接先看action和reducer都干了些什么工作
  loadConfig,
  loadMaps,
  loadNotifications,
  loadSessions,
  loadFriendsSchedules,
  loadSurveys,
} = require('./actions');//加载初始化配置的action
//这个地方的初始化的一些state是在这里加载的,对比ireading软件的//内容加载是在组件中的componentdidMount加载的,放在这里性能是不//是有些优化,
var { updateInstallation } = require('./actions/installation');
var { connect } = require('react-redux');//connect函数
//没想到在这里也是可以用的,在f8app中那个组件要使用state和dispatch就在哪里导入connect函数.

var { version } = require('./env.js');//获取当前版本号码

var F8App = React.createClass({
  componentDidMount: function() {//监听change事件
    AppState.addEventListener('change', this.handleAppStateChange);

    // TODO: Make this list smaller, we basically download the whole internet
    //这个地方在先于UI组件之前加载了所有的state,
    //根据UI导航的默认项是session,我觉得这里可以先加载//session这个state,在首页加载的时候速度就快了,其他的//state在切换到需要某部分的state的时候在加载
    //是不是惰性加载的意思?
    this.props.dispatch(loadNotifications());
    this.props.dispatch(loadMaps());
    this.props.dispatch(loadConfig());
    this.props.dispatch(loadSessions());
    this.props.dispatch(loadFriendsSchedules());
    this.props.dispatch(loadSurveys());

    updateInstallation({version});//热更新版本.
    CodePush.sync({installMode: CodePush.InstallMode.ON_NEXT_RESUME});
  },

  componentWillUnmount: function() {//移除change事件
    AppState.removeEventListener('change', this.handleAppStateChange);
  },

  handleAppStateChange: function(appState) {
    if (appState === 'active') {//在线就分发动作
      this.props.dispatch(loadSessions());
      this.props.dispatch(loadNotifications());
      this.props.dispatch(loadSurveys());
      CodePush.sync({installMode: CodePush.InstallMode.ON_NEXT_RESUME});
    }
  },

  render: function() {
    //下面这个地方对于初学redux的就有点绕了
    //在reducer/user.js中定义了初始化的state,isLoggedIn
    //就是false,所以初次加载的时候就会显示登录按钮了
    //如果登录了以后根据登录回调函数的返回值来修改isLoggedIn的state为//true,同时由于还使用了redux-presist的组件持久化state,在下一//次打开的时候就不会显示登录按钮了
    if (!this.props.isLoggedIn) {
      return <LoginScreen />;
    }
    return (//如果已经登录过就直接显示导航界面了.
      <View style={styles.container}>
        <StatusBar
          translucent={true}
          backgroundColor="rgba(0, 0, 0, 0.2)"
          barStyle="light-content"
         />
        <F8Navigator />
        <PushNotificationsController />
      </View>
    );
  },

});

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

function select(store) {//这里只需要获取是否登录的state就可以了
  //select函数截取和映射组件需要的那部分state,这和数据库是一样的
  //select * from 获取所有的数据, 加了where条件就会返回一部分数据
  return {
    isLoggedIn: store.user.isLoggedIn || store.user.hasSkippedLogin,
  };
}

module.exports = connect(select)(F8App);

connect文件的源码的一点研究-函数式编程的启蒙

如果你对于react-redux有了一定了解,会在connect()中找dispatch在哪里?实际上如果没有mapDiaptchToProps也是可以工作的,在node_modules/react-redux/commponents/connect.js
connect.js

//部分代码
 const defaultMapDispatchToProps = dispatch => ({ dispatch })//不传递也是可以的
 
 let mapDispatch
  if (typeof mapDispatchToProps === 'function') {
    mapDispatch = mapDispatchToProps
  } else if (!mapDispatchToProps) {//如果没传dispatch函数
  //就是默认的参数,还是dispatch
    mapDispatch = defaultMapDispatchToProps
  } else {
    mapDispatch = wrapActionCreators(mapDispatchToProps)
  }

入口文件和初始化配置和加载项就看这么多,下面我们直接先跳到reducer目录
看看数据state是怎么组织的.

Reducer文件夹的内容

图片2 reducer的结构目录
图片2 reducer的结构目录

除了测试文件夹和假数据文件夹和createParseReducer.js文件,其他的文件都导入到index.js文件

./F8App/reducers/index.js

 'use strict';

var { combineReducers } = require('redux');
//每个导入的文件都是对象,combinReducers函数负责把小对象合成一个大的单一对象.
//如果是多人开发我觉得可以每个开发者:一个组件-一组相关的actions-
//-一个单一的reducer,如果比喻的话像是一根粗绳子,实际是有小股的绳子拧在
//一起形成的,各自在出力,旁边的小股如果出问题了不影响其他部分.
module.exports = combineReducers({
  config: require('./config'),
  notifications: require('./notifications'),
  maps: require('./maps'),
  sessions: require('./sessions'),
  user: require('./user'),
  schedule: require('./schedule'),
  topics: require('./topics'),
  filter: require('./filter'),
  navigation: require('./navigation'),
  friendsSchedules: require('./friendsSchedules'),
  surveys: require('./surveys'),
});

//现在在我眼里,index.js就是一个数据库,其中的每一个reducer就是一张、数
//据表,这么比喻我觉得还是可以接受的,有助于概念的理解
  • 在数据库中数据库的名字只是一个标示,真正实现具体内容的是每张表的内容,所以要具体看看每个表(state)都是有哪些内容.知道了表里的内容,就可以相应的理解对于表的操作方法(action).

  • combinReducers的源码实际就是合并对象的,如果是简单的对象还好处理,复杂在上面的每一个reducer实际还可以合并更小的reducer,这对于大型项目的state结构有好处,但是源码不太好理解,我没搞懂,而且更大型的state的组织形式还没有看到那个源码使用,半路出家的基础薄弱,这个地方有点免为其难了,不过怎么做应该是很好操作的.以后努力熟悉这个方面的内容.这个是state的组织形式.在redux和react-redux中到处弥漫着函数式编程的思想,可惜道行不深,无法完全理解.留待日后再说O(∩_∩)O~.

  • 单独看每个导入的reducer就涉及到了具体的逻辑了.这部分的reducer的文件名和action中的文件名是对应的.所以在看这部分的时候要两个文件一起打开看.我在mac上使用了sizeUP软件,点击两个文件以后,选left或者right就可以把两个文件视图平分到屏幕上,不用手动去拉视窗大小,非常方便.

action和reducer配对出现

login.js action和user.js reducer

user.js

 'use strict';

import type {Action} from '../actions/types';

export type State = {//类型约束
  isLoggedIn: boolean;
  hasSkippedLogin: boolean;
  sharedSchedule: ?boolean;
  id: ?string;
  name: ?string;
};

const initialState = {//初始化的state
  isLoggedIn: false,
  hasSkippedLogin: false,
  sharedSchedule: null,
  id: null,
  name: null,
};

function user(state: State = initialState, action: Action): State {
  if (action.type === 'LOGGED_IN') {处理登录的action
    //获取action的负载内容
    let {id, name, sharedSchedule} = action.data;
    if (sharedSchedule === undefined) {
      sharedSchedule = null;
    }
    return {
      isLoggedIn: true, //根据这个属性就可以在UI中做出相应的改变了
      hasSkippedLogin: false,
      sharedSchedule,
      id,//下面这两个参数要在到达reducer之前获得,所以在action中
      //执行远程数据的获取
      name,
    };
  }
  if (action.type === 'SKIPPED_LOGIN') {//跳过登录的action
    return {
      isLoggedIn: false,
      hasSkippedLogin: true,//改变这个属性
      sharedSchedule: null,
      id: null,
      name: null,
    };
  }
  if (action.type === 'LOGGED_OUT') {//处理登出的action
    return initialState;//返回初始值就可以了
  }
  if (action.type === 'SET_SHARING') {//设置分享的action
    return {
      ...state,
      sharedSchedule: action.enabled,//组件根据这个state就//可以决定是可以分享还是不可以分享
    };
  }
  if (action.type === 'RESET_NUXES') {
    return {...state, sharedSchedule: null};
  }
  return state;
}

module.exports = user;

user的reducer还比较简单,最后一句module.expors=user;使用了javascript中函数是第一类对象的定义,user是这个函数对象的引用
那么user对象中是什么呢?😁,里面是闭包啊! 要不然combineReducers里面调用user函数的时候state能记住改变的内容呢?要理解这些概念,对javascript的闭包的理解是不可少的.

login.js

 use strict';

const Parse = require('parse/react-native');//连接parse的客户端包
const FacebookSDK = require('FacebookSDK');
const ActionSheetIOS = require('ActionSheetIOS');//上拉组件
const {Platform} = require('react-native');//
const Alert = require('Alert');
const {restoreSchedule, loadFriendsSchedules} = require('./schedule');
const {updateInstallation} = require('./installation');
const {loadSurveys} = require('./surveys');

import type { Action, ThunkAction } from './types';
//下面两个函数是使用ParseFacebook登录的异步操作
async function ParseFacebookLogin(scope): Promise {
  return new Promise((resolve, reject) => {
    Parse.FacebookUtils.logIn(scope, {
      success: resolve,
      error: (user, error) => reject(error && error.error || error),
    });
  });
}

async function queryFacebookAPI(path, ...args): Promise {
  return new Promise((resolve, reject) => {
    FacebookSDK.api(path, ...args, (response) => {
      if (response && !response.error) {
        resolve(response);
      } else {
        reject(response && response.error);
      }
    });
  });
}

async function _logInWithFacebook(source: ?string): Promise<Array<Action>> {
  await ParseFacebookLogin('public_profile,email,user_friends');//es7的异步操作
  const profile = await queryFacebookAPI('/me', {fields: 'name,email'});

  const user = await Parse.User.currentAsync();
  user.set('facebook_id', profile.id);
  user.set('name', profile.name);
  user.set('email', profile.email);
  await user.save();
  await updateInstallation({user});

  const action = {//配置action对象
    type: 'LOGGED_IN', //传到reducer的actioType
    source,
    data: {
      id: profile.id,
      name: profile.name,
      sharedSchedule: user.get('sharedSchedule'),
    },
  };

  return Promise.all([
    Promise.resolve(action),
    restoreSchedule(),
  ]);
}
//这一步就有点绕了,由于是远程操作,需要异步处理,等待结果以后才能dispatch
//获取的结果,
function logInWithFacebook(source: ?string): ThunkAction {
  return (dispatch) => {
    const login = _logInWithFacebook(source);//返回的就是上面的const action
    
    // Loading friends schedules shouldn't block the login process
    login.then(
      (result) => {
        dispatch(result);
        dispatch(loadFriendsSchedules());
        dispatch(loadSurveys());
      }
    );
    return login;
  };
}

function skipLogin(): Action {//跳过登录的action
  return {
    type: 'SKIPPED_LOGIN',
  };
}

function logOut(): ThunkAction {//登出也是异步操作,等待两个远程数据的相应结果
  return (dispatch) => {
    Parse.User.logOut();
    FacebookSDK.logout();
    updateInstallation({user: null, channels: []});

    // TODO: Make sure reducers clear their state
    return dispatch({
      type: 'LOGGED_OUT',
    });
  };
}

function logOutWithPrompt(): ThunkAction {//对话框退出,也是异步操作
  //确认以后再dispatch一个action
  return (dispatch, getState) => {
    let name = getState().user.name || 'there';

    if (Platform.OS === 'ios') {
      ActionSheetIOS.showActionSheetWithOptions(
        {
          title: `Hi, ${name}`,
          options: ['Log out', 'Cancel'],
          destructiveButtonIndex: 0,
          cancelButtonIndex: 1,
        },
        (buttonIndex) => {
          if (buttonIndex === 0) {//根据逻辑判dispatch退出操作
            dispatch(logOut());
          }
        }
      );
    } else {
      Alert.alert(//andoid弹出对话框
        `Hi, ${name}`,
        'Log out from F8?',
        [
          { text: 'Cancel' },
          { text: 'Log out', onPress: () => dispatch(logOut()) },
        ]
      );
    }
  };
}
//初看代码logInWithFacebook这个action是没有在ationType中的,但是其实
//这个函数有返回了LoginedIn对象,这一点有仔细看看
module.exports = {logInWithFacebook, skipLogin, logOut, logOutWithPrompt};


登录的具体逻辑和实现就是这些.UI组件要怎么做呢?好了我这里在举一个生活中的例子来说明.这个思想从我的电灯模型开始演化成为了自动贩卖机模型或者ATM机模型了. 我们姑且称为ATM机模型好了.回想你去ATM取钱,输入密码,输入钱的数目.你是大款一次想取十万块,点击按键或者触摸屏输入十万块,可惜ATM的钱箱没有装那么多,告诉你ATM机没有这么多钱,这个消息反应的ATM钱箱的state是没有十万.于是你按键或者触摸操作输入1000块,于是乎ATM机器给你吐出了1000块.你的账户的余额state减掉了1000块.这个过程居然和counter的过程是一样的.那么为什么要介绍这个模型呢?我们按键或者触摸操作,并没有在屏幕上实现具体的操作,钞票的制作,钱箱的打开,钞票的数量计算都是有机器来完成的.我们点击的屏幕和按键其实就是UI用户界面,界面上的按键其实只是实际操作的代理.javacript的函数可以传引用赋值,我们就可以使用函数名字来调用实际的函数具体操作.尽管屏幕上没有钱箱,但是我们却可以取到钱.从这个例子看,redux是多么的简单.但是琢磨出这个原理也是花了很长时间的.这实际就是中介者模式.
哈哈那个钱箱就是一个闭包,你看看是不是?

Actions文件夹中的工具文件parse.js

parse.js

 

'use strict';

const Parse = require('parse/react-native');//parseServer的客户端
const logError = require('logError');
const InteractionManager = require('InteractionManager');

import type { ThunkAction } from './types';

const Maps = Parse.Object.extend('Maps');//ParseServer的对象
//可以参看ireading app feedback模块里的反馈意见的远程存储,使用的是leancloud
//但是API是完全一样的.
const Notification = Parse.Object.extend('Notification');
//总的ParseQuery查询函数,根据type和查询筛选结果动态dispatch action
function loadParseQuery(type: string, query: Parse.Query): ThunkAction {
  return (dispatch) => {
    return query.find({
      success: (list) => {
        // We don't want data loading to interfere with smooth animations
        InteractionManager.runAfterInteractions(() => {
          // Flow can't guarantee {type, list} is a valid action
          dispatch(({type, list}: any));
        });
      },
      error: logError,
    });
  };
}

module.exports = {
//load就是tabbar默认tab加载的会议日程state
  loadSessions: (): ThunkAction =>//加载会议日程,这里不是浏览器的session,映射
    loadParseQuery(
      'LOADED_SESSIONS',
      new Parse.Query('Agenda')
        .include('speakers')
        .ascending('startTime')
    ),

  loadMaps: (): ThunkAction =>//映射
    loadParseQuery('LOADED_MAPS', new Parse.Query(Maps)),

  loadNotifications: (): ThunkAction =>//映射对象
    loadParseQuery('LOADED_NOTIFICATIONS', new Parse.Query(Notification)),
};

actions/config.js

config.js

use strict';

const Parse = require('parse/react-native');
const InteractionManager = require('InteractionManager');

import type { Action } from './types'; //获得所有的type

//获取配置文件的异步操作
async function loadConfig(): Promise<Action> {
  const config = await Parse.Config.get();// 从parse服务器获取配置数据
  await InteractionManager.runAfterInteractions();
  return {//操作逻辑在这里返回config数据然后拷贝到state
    type: 'LOADED_CONFIG',
    config,//配置负载或者载荷
  };
}

module.exports = {loadConfig};
 

推荐阅读更多精彩内容