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文件夹的内容
除了测试文件夹和假数据文件夹和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};