用 ReactXP
加速跨平台開發
黃 胤翔
Ben
- Web 全端開發
- 趨勢科技
- CTJS Conf Staff
- React Native Taiwan
- https://github.com/hinxcode
Slides Link
slides.com/hinx/reactxp
OUTLINE
- Introduction
- Why We Need ReactXP
- Demo
- Dive into ReactXP
- Other Solutions
- Conclusion
25 mins
身為一個
Web Developer
一個會 React
的前端工程師
Learn once,
write anywhere
View maps directly to the native view equivalent on whatever platform React Native is running on, whether that is a UIView, <div>, android.view, etc.
https://facebook.github.io/react-native/docs/view.html
Is it possible compatible with Web?
Android
iOS
Web
Non-Platform
Specific
A Library For Building Cross-Platform Apps
ReactXP
ReactXP
X-Platform
https://microsoft.github.io/reactxp/docs/faq.html
ReactXP builds upon React Native. ReactXP’s components and APIs are inspired by React Native.
AppRegistry
import { AppRegistry } from 'react-native';
import App from './App';
AppRegistry.registerComponent('MyApp', () => App);
https://github.com/Microsoft/reactxp/blob/master/samples/hello-world-js/src/index.js
https://github.com/Microsoft/reactxp/blob/master/src/native-common/App.ts
import React from 'react';
import RX from 'reactxp';
import App from './App';
RX.initialize(true, true);
RX.UserInterface(<App />);
import RN from 'react-native';
...
initialize(debug: boolean, development: boolean) {
super.initialize(debug, development);
window['rxdebug'] = debug;
RN.AppRegistry.registerComponent('RXApp', () => RootView);
}
...
API
Props
Styles
Attributes
Animation Interface
Components
Android
iOS
Web
React Native
ReactXP
React Native
ReactXP
Android
iOS
Web
React
Webpack
Platform Support
-
iOS (React Native)
-
Android (React Native)
-
Web (React)
-
Desktop (Electron)
Browser Support
-
Chrome
-
IE (9 and newer)
-
Edge
-
Firefox
-
Safari
-
Other HTML5 browsers
DEMO
https://github.com/hinxcode/coscup-schedule-app
iOS App
Android App
Mobile Web
Desktop Web
Live Demo
跨三平台 成功!
ReactXP Overview
https://github.com/Microsoft/reactxp
https://microsoft.github.io/reactxp
Repo
Docs
Use ReactXP
Without TS ?
Components
- ActivityIndicator
- Button
- GestureView
- Image
- Link
- Navigator
- Picker
- ScrollView
- Text
- TextInput
- View
- WebView
ActivityIndicator
https://github.com/Microsoft/reactxp/blob/master/src/native-common/ActivityIndicator.tsx
return (
<RN.ActivityIndicator
animating={ this.state.isVisible }
color={ this.state.isVisible ? this.props.color : 'transparent' }
size={ size }
/>
);
GestureView
-
Tapping
-
Double-tapping
-
Panning
-
Pinching
Navigator
-
Stack
-
cardStyle
-
transitionCompleted()
-
renderScene()
Picker
-
items
-
onValueChange
Styles
const myViewStyle = RX.Styles.createViewStyle({
backgroundColor: '#fff'
});
<RX.View style={myViewStyle} />
createXXXStyle()
/*
{ backgroundColor: '#fff' } <-- ruleSet
*/
createViewStyle(ruleSet: Types.ViewStyle, cacheStyle: boolean = true): Types.ViewStyleRuleSet {
return this._adaptStyles(ruleSet, cacheStyle);
}
https://github.com/Microsoft/reactxp/blob/master/src/native-common/Styles.ts#L71
Types
export interface ViewStyle extends ViewAndImageCommonStyle {
borderStyle?: 'solid' | 'dotted' | 'dashed' | 'none';
...
}
export interface ViewAndImageCommonStyle extends FlexboxStyle, TransformStyle {
borderWidth?: number;
borderColor?: string;
borderRadius?: number;
...
}
export interface FlexboxStyle {
...
}
https://github.com/Microsoft/reactxp/blob/master/src/common/Types.ts
createViewStyle(ruleSet: Types.ViewStyle, cacheStyle: boolean = true): Types.ViewStyleRuleSet {
return this._adaptStyles(ruleSet, cacheStyle);
}
_adaptStyles()
private _adaptStyles<S extends Types.ViewAndImageCommonStyle>(def: S, cacheStyle: boolean): Types.StyleRuleSet<S> {
let adaptedRuleSet = def as ReactNativeViewAndImageCommonStyle<S>;
...
// Convert text styling
let textStyle = adaptedRuleSet as Types.TextStyle;
if (textStyle.font) {
if (textStyle.font.fontFamily !== undefined) {
textStyle.fontFamily = textStyle.font.fontFamily;
}
if (textStyle.font.fontWeight !== undefined) {
textStyle.fontWeight = textStyle.font.fontWeight;
}
if (textStyle.font.fontStyle !== undefined) {
textStyle.fontStyle = textStyle.font.fontStyle;
}
delete textStyle.font;
}
if (def.flex !== undefined) {
var flexValue = def.flex;
delete adaptedRuleSet.flex;
if (flexValue > 0) {
// p 1 auto
adaptedRuleSet.flexGrow = flexValue;
adaptedRuleSet.flexShrink = 1;
} else if (flexValue < 0) {
// 0 -n auto
adaptedRuleSet.flexGrow = 0;
adaptedRuleSet.flexShrink = -flexValue;
} else {
// 0 0 auto
adaptedRuleSet.flexGrow = 0;
adaptedRuleSet.flexShrink = 0;
}
}
...
return adaptedRuleSet;
}
https://github.com/Microsoft/reactxp/blob/master/src/native-common/Styles.ts#L135
cacheStyle
let dynamicViewStyle = RX.Styles.createViewStyle({
backgroundColor: userColor
}, false);
createViewStyle(ruleSet: Types.ViewStyle, cacheStyle: boolean = true): Types.ViewStyleRuleSet {
return this._adaptStyles(ruleSet, cacheStyle);
}
Animations
Animated Value
constructor(props) {
...
this._translationValue = new RX.Animated.Value(-100);
}
Animated Styles
constructor(props) {
...
this._animatedStyle = RX.Styles.createAnimatedTextStyle({
transform: [{
translateY: this._translationValue
}]
});
}
render() {
<RX.Animated.Text style={ this._animatedStyle }>
Hello World
</RX.Animated.Text>
}
Timing Animations
constructor(props) {
...
this._translationValue = new RX.Animated.Value(-100);
}
componentDidMount() {
let animation = RX.Animated.timing(this._translationValue, {
toValue: 0,
easing: RX.Animated.Easing.OutBack(),
duration: 500
}
);
animation.start();
}
Composite Animations
let compositeAnimation = RX.Animated.parallel([
RX.Animated.timing(animatedScaleValue,
{ toValue: 0.0, duration: 250, easing: RX.Animated.Easing.InOut() }
),
RX.Animated.timing(animatedOpacityValue,
{ toValue: 1.1, duration 250, easing: RX.Animated.Easing.Linear() }
)
]);
compositeAnimation.start();
Sometimes it’s useful to execute multiple animations in parallel or in sequence.
import RN = require('react-native');
...
export var Animated = {
...
delay: RN.Animated.delay,
parallel: RN.Animated.parallel,
sequence: RN.Animated.sequence
};
https://github.com/Microsoft/reactxp/blob/master/src/native-common/Animated.tsx#L194
Composite Animations
APIs
- Accessibility
- Alert
- App
- Clipboard
- Input
- International
- Linking
- Location
- Modal
- Network
- Platform
- Popup
- StatusBar
- Storage
- UserInterface
- UserPresence
App
/* Methods */
initialize(debug: boolean, development: boolean): void;
getActivationState(): AppActivationState;
...
https://microsoft.github.io/reactxp/docs/apis/app.html
import React from 'react';
import RX from 'reactxp';
import App from './App';
RX.App.initialize(true, true);
RX.UserInterface.setMainView(<App />);
UserInterface
/* Methods */
setMainView(element: React.ReactElement<any>): void;
useCustomScrollbars(enable: boolean): void;
isHighPixelDensityScreen(): boolean;
getContentSizeMultiplier(): SyncTasks.Promise<number>;
getMaxContentSizeMultiplier(): SyncTasks.Promise<number>;
setMaxContentSizeMultiplier(maxContentSizeMultiplier: number): void;
dismissKeyboard(): void;
isNavigatingWithKeyboard(): boolean;
...
https://microsoft.github.io/reactxp/docs/apis/userinterface.html
Linking
/* Types */
interface SmsInfo {
phoneNumber?: string;
body?: string;
}
interface EmailInfo {
to?: string[];
cc?: string[];
bcc?: string[];
subject?: string;
body?: string;
}
interface LinkingErrorInfo {
code: LinkingError;
url: string;
error?: string;
}
enum LinkingErrorCode {
NoAppFound = 0,
UnexpectedFailure = 1,
Blocked = 2,
InitialUrlNotFound = 3
}
/* Methods */
openUrl(url: string): SyncTasks.Promise<void>;
launchSms(smsData: SmsInfo): SyncTasks.Promise<void>;
launchEmail(emailData: EmailInfo): SyncTasks.Promise<void>;
...
https://microsoft.github.io/reactxp/docs/apis/linking.html
Network
/* Types */
enum DeviceNetworkType {
Unknown,
None,
Wifi,
Mobile2G,
Mobile3G,
Mobile4G
}
/* Methods */
isConnected(): SyncTasks.Promise<boolean>;
getType(): SyncTasks.Promise<DeviceNetworkType>;
/* Events */
connectivityChangedEvent: SubscribableEvent<(isConnected: boolean) => void>;
https://microsoft.github.io/reactxp/docs/apis/network.html
Platform
/* Types */
type PlatformType = 'web' | 'ios' | 'android' | 'windows';
/* Methods */
getType(): Types.PlatformType;
https://microsoft.github.io/reactxp/docs/apis/platform.html
UserPresence
/* Methods */
isUserPresent(): boolean;
/* Events */
userPresenceChangedEvent: SubscribableEvent<
(isPresent: boolean) => void>();
https://microsoft.github.io/reactxp/docs/apis/userpresence.html
Extensions
-
ImageSvg
-
Video
-
VirtualListView
Primitive Extensions
import RXVideoPlayer from 'reactxp-videoplayer';
class MyVideoPanel extends RX.Component<null, null> {
render() {
return (
<RXVideoPlayer
source={ this.props.source }
showControls={ true }
onProgress={ this._onVideoProgress }
onEnded={ this._onVideoEnded }
/>
);
}
}
Higher-level
Component
Contain no platform-specific code but build upon the lower-level primitives
Other Solutions
react-web
https://github.com/taobaofed/react-web
react-primitives
https://github.com/lelandrichardson/react-primitives
react-native-web
https://github.com/necolas/react-native-web
ReactXP Project Generator
https://github.com/react-native-training/create-xp-app
https://www.facebook.com/groups/reactnativetw
References
- https://microsoft.github.io/reactxp/
- https://facebook.github.io/react-native/
- https://blog.souche.com/react-native-source-code-analysis/
- http://www.infoq.com/cn/articles/react-native-web
Thanks for Listening
ReactXP
By hinx
ReactXP
2017 COSCUP
- 1,458