美文网首页Web前端之路程序员技术干货
乱花渐欲迷人眼,返璞归真F8(2)

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

作者: smartphp | 来源:发表于2017-04-06 20:06 被阅读248次

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

    相关文章

      网友评论

        本文标题:乱花渐欲迷人眼,返璞归真F8(2)

        本文链接:https://www.haomeiwen.com/subject/ivoqattx.html