跳到主要内容

React Native 中更好的列表视图

·6 分钟阅读

在我们在社区群组中发布预告后,许多人已经开始使用我们的一些新的列表组件了,但我们今天正式宣布它们!不再有ListViewDataSource、过时行、被忽略的 bug 或过多的内存消耗——通过最新的 React Native 2017 年 3 月发布候选版本(0.43-rc.1),您可以从新的组件套件中选择最适合您的用例的组件,开箱即用,具有出色的性能和功能集。

<FlatList>

这是用于简单、高性能列表的主力组件。提供一个数据数组和一个renderItem函数,就可以开始使用了。

<FlatList
data={[{title: 'Title Text', key: 'item1'}, ...]}
renderItem={({item}) => <ListItem title={item.title} />}
/>

<SectionList>

如果您想渲染一组分解成逻辑部分的数据,可能带有部分标题(例如,在字母地址簿中),并且可能带有异构数据和渲染(例如,带有按钮的个人资料视图,然后是作曲家,然后是照片网格,然后是朋友网格,最后是故事列表),这是最佳选择。

<SectionList
renderItem={({item}) => <ListItem title={item.title} />}
renderSectionHeader={({section}) => <H1 title={section.key} />}
sections={[ // homogeneous rendering between sections
{data: [...], key: ...},
{data: [...], key: ...},
{data: [...], key: ...},
]}
/>

<SectionList
sections={[ // heterogeneous rendering between sections
{data: [...], key: ..., renderItem: ...},
{data: [...], key: ..., renderItem: ...},
{data: [...], key: ..., renderItem: ...},
]}
/>

<VirtualizedList>

更灵活的 API 的幕后实现。如果您的数据不是纯数组(例如,一个不可变列表),则特别方便。

功能

列表在许多情况下使用,因此我们为新组件提供了许多功能,以处理大多数开箱即用的用例。

  • 滚动加载 (onEndReached)。
  • 下拉刷新 (onRefresh / refreshing)。
  • 可配置的可见性(VPV)回调(onViewableItemsChanged / viewabilityConfig)。
  • 水平模式 (horizontal)。
  • 智能项目和节分隔符。
  • 多列支持 (numColumns)
  • scrollToEndscrollToIndexscrollToItem
  • 更好的 Flow 类型。

一些注意事项

  • 当内容滚动出渲染窗口时,项目子树的内部状态不会保留。确保所有数据都捕获在项目数据或外部存储中,如 Flux、Redux 或 Relay。

  • 这些组件基于PureComponent,这意味着如果props保持浅层相等,它们将不会重新渲染。确保您的renderItem函数直接依赖的所有内容都作为在更新后不是===的 props 传递,否则您的 UI 可能不会在更改时更新。这包括data prop 和父组件状态。例如

    <FlatList
    data={this.state.data}
    renderItem={({item}) => (
    <MyItem
    item={item}
    onPress={() =>
    this.setState(oldState => ({
    selected: {
    // New instance breaks `===`
    ...oldState.selected, // copy old data
    [item.key]: !oldState.selected[item.key], // toggle
    },
    }))
    }
    selected={
    !!this.state.selected[item.key] // renderItem depends on state
    }
    />
    )}
    selected={
    // Can be any prop that doesn't collide with existing props
    this.state.selected // A change to selected should re-render FlatList
    }
    />
  • 为了限制内存并实现平滑滚动,内容在屏幕外异步渲染。这意味着滚动速度可能快于填充率,并暂时看到空白内容。这是一个权衡,可以根据每个应用程序的需求进行调整,我们正在努力在幕后改进它。

  • 默认情况下,这些新列表会在每个项目上查找一个key prop,并将其用作 React 键。或者,您可以提供一个自定义的keyExtractor prop。

性能

除了简化 API,新的列表组件还具有显著的性能增强,主要的一点是对于任意数量的行,内存使用量几乎恒定。这是通过“虚拟化”渲染窗口之外的元素来实现的,通过将它们从组件层次结构中完全卸载,并回收 React 组件的 JS 内存,以及阴影树和 UI 视图的本机内存。这有一个注意事项,即内部组件状态将不会保留,因此**请确保您在组件本身之外跟踪任何重要的状态,例如在 Relay 或 Redux 或 Flux 存储中。**

限制渲染窗口也减少了 React 和原生平台需要完成的工作量,例如视图遍历。即使您正在渲染数百万个元素中的最后一个,使用这些新列表也不需要迭代所有这些元素来渲染。您甚至可以使用scrollToIndex跳转到中间而不会过度渲染。

我们还对调度做了一些改进,这应该有助于提高应用程序的响应性。渲染窗口边缘的项很少渲染,并且在任何活动手势、动画或其他交互完成后以较低的优先级渲染。

高级用法

ListView不同,渲染窗口中的所有项目在任何 props 更改时都会重新渲染。通常这没问题,因为窗口化将项目数量减少到恒定数量,但如果您的项目比较复杂,您应该确保遵循 React 性能最佳实践,并在您的组件中酌情使用React.PureComponent和/或shouldComponentUpdate来限制递归子树的重新渲染。

如果您可以在不渲染行的情况下计算行的高度,您可以通过提供getItemLayout属性来改善用户体验。这使得使用例如scrollToIndex滚动到特定项目更加流畅,并且会改善滚动指示器 UI,因为内容的高度可以在不渲染的情况下确定。

如果您有替代数据类型,例如不可变列表,则<VirtualizedList>是最佳选择。它接受一个getItem prop,允许您返回任何给定索引的项目数据,并且具有更宽松的 Flow 类型。

如果您有不寻常的用例,还可以调整许多参数。例如,您可以使用windowSize来权衡内存使用和用户体验,maxToRenderPerBatch来调整填充率和响应性,onEndReachedThreshold来控制何时发生滚动加载,等等。

未来工作

  • 现有界面的迁移(最终废弃ListView)。
  • 根据我们的需求/意见增加更多功能(请告诉我们!)。
  • 粘性节标题支持。
  • 更多性能优化。
  • 支持带状态的功能项目组件。

idx:存在函数

·阅读时长2分钟
Timothy Yung
Facebook 工程经理

在 Facebook,我们经常需要访问通过 GraphQL 获取的数据结构中深度嵌套的值。在访问这些深度嵌套的值时,一个或多个中间字段为空是很常见的。这些中间字段为空的原因有很多,从隐私检查失败到仅仅是因为 null 是表示非致命错误的最灵活的方式。

不幸的是,访问这些深度嵌套的值目前既繁琐又冗长。

props.user &&
props.user.friends &&
props.user.friends[0] &&
props.user.friends[0].friends;

有一个ECMAScript 提案引入了存在运算符,这将使其更加方便。但在该提案最终确定之前,我们希望有一个解决方案,可以改善我们的生活质量,保持现有语言语义,并鼓励 Flow 的类型安全。

我们想出了一个名为 idx 的存在性函数

idx(props, _ => _.user.friends[0].friends);

此代码片段中的调用与上面代码片段中的布尔表达式的行为类似,但重复性大大降低。idx 函数严格接受两个参数

  • 任何值,通常是您要访问嵌套值的对象或数组。
  • 一个函数,它接收第一个参数并访问其上的嵌套值。

理论上,idx函数将捕获访问 null 或 undefined 上的属性所导致的错误。如果捕获到此类错误,它将返回 null 或 undefined。(您可以在idx.js中看到它可能如何实现。)

实际上,对每个嵌套属性访问进行 try-catch 是缓慢的,并且区分特定类型的 TypeError 是脆弱的。为了解决这些缺点,我们创建了一个 Babel 插件,将上面的idx调用转换为以下表达式

props.user == null
? props.user
: props.user.friends == null
? props.user.friends
: props.user.friends[0] == null
? props.user.friends[0]
: props.user.friends[0].friends;

最后,我们为 idx 添加了一个自定义的 Flow 类型声明,允许在第二个参数中进行遍历的类型检查,同时允许对可空属性进行嵌套访问。

该函数、Babel 插件和 Flow 声明现已在 GitHub 上提供。它们通过安装**idx**和**babel-plugin-idx** npm 包,并将“idx”添加到您的.babelrc文件中的插件列表中来使用。

引入 Create React Native App

·3 分钟阅读
Adam Perry
Expo 软件工程师

今天我们宣布推出Create React Native App:一个新工具,让您更轻松地开始 React Native 项目!它深受Create React App设计理念的启发,是FacebookExpo(原 Exponent)合作的产物。

许多开发者在安装和配置 React Native 当前的原生构建依赖项时遇到困难,尤其是在 Android 上。使用 Create React Native App,无需使用 Xcode 或 Android Studio,您就可以使用 Linux 或 Windows 为您的 iOS 设备开发。这是通过 Expo 应用程序实现的,它加载并运行纯 JavaScript 编写的 CRNA 项目,无需编译任何原生代码。

尝试创建一个新项目(如果已安装 yarn,请替换为合适的 yarn 命令)

$ npm i -g create-react-native-app
$ create-react-native-app my-project
$ cd my-project
$ npm start

这将启动 React Native 打包器并打印一个二维码。在Expo 应用程序中打开它以加载您的 JavaScript。console.log的调用会转发到您的终端。您可以使用任何标准 React Native API 以及Expo SDK

原生代码怎么办?

许多 React Native 项目都有需要编译的 Java 或 Objective-C/Swift 依赖项。Expo 应用程序确实包含用于相机、视频、联系人等的 API,并捆绑了流行的库,例如Airbnb 的 react-native-mapsFacebook 身份验证。但是,如果您需要 Expo 不捆绑的本机代码依赖项,那么您可能需要为它拥有自己的构建配置。就像 Create React App 一样,CRNA 支持“弹出”。

您可以运行npm run eject来获得一个与react-native init生成的文件非常相似的项目。届时,您将需要 Xcode 和/或 Android Studio,就像您从react-native init开始一样,使用react-native link添加库将起作用,并且您将完全控制本机代码编译过程。

问题?反馈?

Create React Native App 现在已经足够稳定,可以普遍使用,这意味着我们非常渴望听到您使用它的体验!您可以在Twitter上找到我,或者在GitHub 仓库上提出问题。非常欢迎拉取请求!

使用原生驱动动画

·6 分钟阅读
Janic Duplessis
App & Flow 软件工程师

在过去的一年里,我们一直致力于提高使用 Animated 库的动画性能。动画对于创造出色的用户体验非常重要,但也可能很难做得好。我们希望让开发者轻松创建高性能动画,而不必担心某些代码会导致卡顿。

这是什么?

Animated API 的设计考虑了一个非常重要的限制:它是可序列化的。这意味着我们可以在动画开始之前将所有关于动画的信息发送到原生端,并允许原生代码在 UI 线程上执行动画,而无需在每一帧都通过桥接。这非常有用,因为一旦动画开始,JS 线程就可以被阻塞,动画仍然会流畅运行。实际上,这种情况经常发生,因为用户代码在 JS 线程上运行,React 渲染也可能长时间锁定 JS。

一些历史...

这个项目始于大约一年前,当时 Expo 在 Android 上构建了 li.st 应用程序。Krzysztof Magiera 受聘在 Android 上构建初始实现。它最终运行良好,li.st 是第一个使用 Animated 交付原生驱动动画的应用程序。几个月后,Brandon Withrow 在 iOS 上构建了初始实现。之后,Ryan Gomba 和我致力于添加缺失的功能,例如支持Animated.event以及修复我们在生产应用程序中使用时发现的错误。这确实是一项社区努力,我要感谢所有参与者以及 Expo 赞助了大部分开发工作。它现在被 React Native 中的Touchable组件以及新发布的React Navigation库中的导航动画所使用。

它是如何工作的?

首先,让我们看看目前使用 Animated 和 JS 驱动程序动画是如何工作的。使用 Animated 时,您声明一个表示要执行的动画的节点图,然后使用驱动程序根据预定义的曲线更新 Animated 值。您还可以通过使用Animated.event将 Animated 值连接到View的事件来更新它。

以下是动画步骤及其发生位置的分解

  • JS:动画驱动器使用 requestAnimationFrame 在每一帧执行,并根据动画曲线计算出的新值更新它驱动的值。
  • JS:计算中间值并将其传递给附加到 View 的 props 节点。
  • JS:使用 setNativeProps 更新 View
  • JS 到原生桥。
  • 原生:更新 UIViewandroid.View

如您所见,大部分工作都在 JS 线程上进行。如果它被阻塞,动画将跳帧。它还需要在每一帧通过 JS 到原生桥来更新原生视图。

原生驱动程序所做的是将所有这些步骤移动到原生。由于 Animated 生成一个动画节点图,因此可以在动画开始时一次性序列化并发送到原生,从而无需回调到 JS 线程;原生代码可以在 UI 线程上直接更新视图,每帧更新。

以下是我们如何序列化动画值和插值节点的示例(不是确切的实现,只是一个示例)。

创建原生值节点,这是将要动画化的值

NativeAnimatedModule.createNode({
id: 1,
type: 'value',
initialValue: 0,
});

创建原生插值节点,这告诉原生驱动程序如何插值一个值

NativeAnimatedModule.createNode({
id: 2,
type: 'interpolation',
inputRange: [0, 10],
outputRange: [10, 0],
extrapolate: 'clamp',
});

创建原生 props 节点,这告诉原生驱动程序它附加到视图的哪个 props

NativeAnimatedModule.createNode({
id: 3,
type: 'props',
properties: ['style.opacity'],
});

连接节点

NativeAnimatedModule.connectNodes(1, 2);
NativeAnimatedModule.connectNodes(2, 3);

将 props 节点连接到视图

NativeAnimatedModule.connectToView(3, ReactNative.findNodeHandle(viewRef));

这样,原生动画模块就拥有了更新原生视图所需的所有信息,而无需转到 JS 计算任何值。

剩下的就是实际启动动画,指定我们想要的动画曲线类型以及要更新的动画值。计时动画也可以通过提前在 JS 中计算动画的每一帧来简化,以使原生实现更小。

NativeAnimatedModule.startAnimation({
type: 'timing',
frames: [0, 0.1, 0.2, 0.4, 0.65, ...],
animatedValueId: 1,
});

现在是动画运行时发生的事情的分解

  • 原生:原生动画驱动程序使用 CADisplayLinkandroid.view.Choreographer 在每一帧执行,并根据动画曲线计算出的新值更新它驱动的值。
  • 原生:计算中间值并将其传递给附加到原生视图的 props 节点。
  • 原生:更新 UIViewandroid.View

如您所见,不再有 JS 线程和桥接,这意味着更快的动画!🎉🎉

我如何在我的应用程序中使用它?

对于普通动画,答案很简单,只需在启动动画时在动画配置中添加 useNativeDriver: true 即可。

之前

Animated.timing(this.state.animatedValue, {
toValue: 1,
duration: 500,
}).start();

之后

Animated.timing(this.state.animatedValue, {
toValue: 1,
duration: 500,
useNativeDriver: true, // <-- Add this
}).start();

动画值只与一个驱动程序兼容,因此如果您在某个值上启动动画时使用原生驱动程序,请确保该值上的每个动画也使用原生驱动程序。

它也适用于Animated.event,如果您有一个动画必须跟随滚动位置,这非常有用,因为如果没有原生驱动程序,它将始终比手势慢一帧,因为 React Native 的异步性质。

之前

<ScrollView
scrollEventThrottle={16}
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { y: this.state.animatedValue } } }]
)}
>
{content}
</ScrollView>

之后

<Animated.ScrollView // <-- Use the Animated ScrollView wrapper
scrollEventThrottle={1} // <-- Use 1 here to make sure no events are ever missed
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { y: this.state.animatedValue } } }],
{ useNativeDriver: true } // <-- Add this
)}
>
{content}
</Animated.ScrollView>

注意事项

目前,并非所有您可以用 Animated 完成的事情都在 Native Animated 中得到支持。主要限制是您只能对非布局属性进行动画处理,例如transformopacity会起作用,但 Flexbox 和位置属性则不会。另一个是Animated.event,它只适用于直接事件,而不适用于冒泡事件。这意味着它不适用于PanResponder,但适用于ScrollView#onScroll之类的东西。

Native Animated 也已成为 React Native 的一部分很长时间了,但由于被认为是实验性的,从未被记录。因此,如果您想使用此功能,请确保您使用的是最新版本 (0.40+) 的 React Native。

资源

有关动画的更多信息,我建议观看Christopher Chedeau 的这次演讲

如果您想深入了解动画以及如何将其卸载到原生以改善用户体验,还可以观看Krzysztof Magiera 的这次演讲

每月发布周期:发布 12 月和 1 月 RC

·3 分钟阅读
Eric Vicenti
Facebook 工程师

React Native 推出后不久,我们开始每两周发布一次,以帮助社区采用新功能,同时保持版本稳定以供生产使用。在 Facebook,我们必须每两周稳定一次代码库,以发布我们的生产 iOS 应用程序,因此我们决定以相同的速度发布开源版本。现在,许多 Facebook 应用程序每周发布一次,尤其是在 Android 上。由于我们每周从 master 发布,我们需要保持它相当稳定。因此,双周发布周期甚至不再使内部贡献者受益。

我们经常从社区那里听到反馈,称发布速度很难跟上。像Expo这样的工具不得不跳过每次发布,以应对版本中的快速变化。因此,很明显双周发布并没有很好地服务于社区。

现在每月发布

我们很高兴地宣布新的月度发布周期,以及 2016 年 12 月发布的v0.40,它在上个月已经稳定下来,可以采用了。(只需确保在 iOS 上更新您的原生模块中的头文件)。

尽管可能会有几天误差以避免周末或处理意外问题,但您现在可以预期某个版本将在该月的第一天可用,并在该月的最后一天发布。

使用当前月份以获得最佳支持

一月份的候选版本已准备好尝试,您可以在此处查看新内容

为了了解即将到来的变化并向 React Native 贡献者提供更好的反馈,请尽可能使用当月的发布候选版本。到每个版本在月末发布时,它所包含的更改已经在生产 Facebook 应用程序中发布了两个多星期。

您可以使用新的 react-native-git-upgrade 命令轻松升级您的应用程序

npm install -g react-native-git-upgrade
react-native-git-upgrade 0.41.0-rc.0

我们希望这种更简单的方法将使社区更容易跟踪 React Native 中的变化,并尽可能快地采用新版本!

(感谢Martin Konicek提出了这个计划,以及Mike Grabowski使其实现)

借助 Git 更轻松地升级

·阅读时长5分钟
Nicolas Cuillery
Zenika 的 JavaScript 顾问和培训师

升级到新版本的 React Native 一直很困难。您可能以前见过类似这样的内容

这些选项都不理想。通过覆盖文件,我们会丢失本地更改。不覆盖则无法获得最新更新。

今天,我很高兴推出一个有助于解决此问题的新工具。该工具名为 react-native-git-upgrade,它在后台使用 Git 尽可能自动解决冲突。

用法

要求:Git 必须在 PATH 中可用。您的项目无需由 Git 管理。

全局安装 react-native-git-upgrade

$ npm install -g react-native-git-upgrade

或者,使用 Yarn

$ yarn global add react-native-git-upgrade

然后,在您的项目目录中运行它

$ cd MyProject
$ react-native-git-upgrade 0.38.0

注意:不要运行“npm install”来安装新版本的react-native。该工具需要能够比较旧的和新的项目模板才能正常工作。只需在您的应用程序文件夹中运行它,如上所示,同时仍在使用旧版本。

输出示例

您也可以在不带任何参数的情况下运行 react-native-git-upgrade,以升级到最新版本的 React Native。

我们尝试保留您在 Android 和 iOS 构建文件中的更改,因此升级后您无需运行 react-native link

我们设计的实现尽可能少地侵入。它完全基于在临时目录中即时创建的本地 Git 仓库。它不会干扰您的项目仓库(无论您使用什么 VCS:Git、SVN、Mercurial,…或不使用)。如果发生意外错误,您的源代码将恢复。

它是如何工作的?

关键步骤是生成一个 Git 补丁。该补丁包含 React Native 模板在您的应用程序所用版本和新版本之间发生的所有更改。

为了获得这个补丁,我们需要从您的node_modules目录中react-native包中嵌入的模板生成一个应用程序(这些模板与react-native init命令使用的模板相同)。然后,在从当前版本和新版本中的模板生成原生应用程序后,Git 能够生成一个适合您的项目的补丁(即包含您的应用程序名称)

[...]

diff --git a/ios/MyAwesomeApp/Info.plist b/ios/MyAwesomeApp/Info.plist
index e98ebb0..2fb6a11 100644
--- a/ios/MyAwesomeApp/Info.plist
+++ b/ios/MyAwesomeApp/Info.plist
@@ -45,7 +45,7 @@
<dict>
<key>localhost</key>
<dict>
- <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
+ <key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
[...]

现在我们只需要将这个补丁应用到您的源文件。虽然旧的react-native upgrade过程会提示您任何小的差异,但 Git 能够使用其 3 向合并算法自动合并大部分更改,最终留下我们熟悉的冲突分隔符

    13B07F951A680F5B00A75B9A /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
<<<<<<< ours
CODE_SIGN_IDENTITY = "iPhone Developer";
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/HockeySDK.embeddedframework",
"$(PROJECT_DIR)/HockeySDK-iOS/HockeySDK.embeddedframework",
);
=======
CURRENT_PROJECT_VERSION = 1;
>>>>>>> theirs
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../node_modules/react-native/React/**",
"$(SRCROOT)/../node_modules/react-native-code-push/ios/CodePush/**",
);

这些冲突通常很容易理解。分隔符 **ours** 代表“您的团队”,而 **theirs** 可以看作是“React Native 团队”。

为什么要引入新的全局包?

React Native 附带一个全局 CLI(react-native-cli包),它将命令委托给嵌入在node_modules/react-native/local-cli目录中的本地 CLI。

如上所述,该过程必须从您当前的 React Native 版本开始。如果我们将实现嵌入到 local-cli 中,那么在使用旧版本 React Native 时,您将无法享受此功能。例如,如果此新升级代码仅在 0.38.0 中发布,您将无法从 0.29.2 升级到 0.38.0。

基于 Git 的升级是开发体验的一大进步,将其提供给每个人非常重要。通过使用单独的包react-native-git-upgrade并全局安装,无论您的项目正在使用哪个版本的 React Native,您今天都可以使用这个新代码。

另一个原因是 Martin Konicek 最近的Yeoman 清理。我们不想将这些 Yeoman 依赖项重新添加到react-native包中,以便能够评估旧模板以创建补丁。

试用并提供反馈

总而言之,我想说,请尽情享受这个功能,并随时提出改进建议、报告问题,特别是发送拉取请求。每个环境都有些不同,每个 React Native 项目也不同,我们需要您的反馈,才能让这项工作对每个人都有效。

谢谢!

我衷心感谢出色的公司ZenikaM6 Web (已存档),没有他们,这一切都不可能实现!

引入 Button、使用 Yarn 更快的安装以及公开路线图

·3 分钟阅读
Héctor Ramos
赫克托·拉莫斯
前 Facebook 开发者倡导者

我们从许多人那里听说 React Native 正在进行大量工作,很难跟上进度。为了帮助沟通正在进行的工作,我们现在发布了React Native 路线图。从高层次来看,这项工作可以分为三个优先级

  • 核心库。为最有用的组件和 API 添加更多功能。
  • 稳定性。改进底层基础设施,减少错误并提高代码质量。
  • 开发者体验。帮助 React Native 开发者更快地行动。

如果您对路线图中认为有价值的功能有建议,请查看Canny,在那里您可以建议新功能并讨论现有提案。

React Native 的新功能

今天发布的React Native 0.37 版引入了一个新的核心组件,使得向任何应用程序添加可触摸按钮变得非常容易。我们还引入了对新Yarn包管理器的支持,这应该会显著加快应用程序依赖项的整个更新过程。

推出 Button

今天我们推出了一个基本的<Button />组件,它在所有平台上都表现出色。这解决了我们收到的最常见的反馈之一:React Native 是少数几个没有开箱即用的按钮的移动开发工具包之一。

Simple Button on Android, iOS

<Button
onPress={onPressMe}
title="Press Me"
accessibilityLabel="Learn more about this Simple Button"
/>

有经验的 React Native 开发者知道如何制作一个按钮:使用 TouchableOpacity 实现 iOS 上的默认外观,使用 TouchableNativeFeedback 实现 Android 上的涟漪效果,然后应用一些样式。自定义按钮的构建或安装并不特别困难,但我们的目标是让 React Native 易学易用。在核心中添加一个基本按钮后,新来者将能够在第一天就开发出一些令人惊叹的东西,而不是花费时间格式化按钮和了解 Touchable 的细微差别。

Button 旨在在每个平台上都能良好运行并看起来原生,因此它不会支持自定义按钮所具备的所有花哨功能。它是一个很好的起点,但并非旨在取代所有现有按钮。要了解更多信息,请查看新的 Button 文档,其中包含一个可运行的示例!

使用 Yarn 加速 react-native init

您现在可以使用Yarn,新的 JavaScript 包管理器,来显著加快react-native init的速度。要查看速度提升,请安装 yarn并将您的react-native-cli升级到 1.2.0

$ npm install -g react-native-cli

现在,在设置新应用时,您应该会看到“Using yarn”(正在使用 yarn)。

Using yarn

在简单的本地测试中,react-native init良好的网络条件下大约 1 分钟内完成(而使用 npm 3.10.8 时大约需要 3 分钟)。安装 yarn 是可选的,但强烈推荐。

谢谢!

我们要感谢所有为本次发布做出贡献的人。完整的发布说明现已在 GitHub 上提供。感谢您,React Native 凭借二十多个错误修复和新功能变得越来越好。

0.36:无头 JS、键盘 API 等

·3 分钟阅读
Héctor Ramos
赫克托·拉莫斯
前 Facebook 开发者倡导者

今天我们发布了 React Native 0.36。继续阅读以了解新功能。

无头 JS

无头 JS 是一种在您的应用程序处于后台时在 JavaScript 中运行任务的方式。例如,它可用于同步新数据、处理推送通知或播放音乐。目前,它仅在 Android 上可用。

要开始使用,请在专用文件(例如 SomeTaskName.js)中定义您的异步任务

module.exports = async taskData => {
// Perform your task here.
};

接下来,在 AppRegistry 上注册您的任务

AppRegistry.registerHeadlessTask('SomeTaskName', () =>
require('SomeTaskName'),
);

使用无头 JS 需要编写一些原生 Java 代码,以便您可以在需要时启动服务。查看我们新的 无头 JS 文档 以了解更多信息!

键盘 API

现在使用Keyboard可以更轻松地处理屏幕键盘。您现在可以监听本机键盘事件并对其做出反应。例如,要关闭活动键盘,只需调用Keyboard.dismiss()

import {Keyboard} from 'react-native';

// Hide that keyboard!
Keyboard.dismiss();

动画除法

React Native 已经支持通过加法、乘法和模运算组合两个动画值。在 0.36 版中,现在可以通过除法组合两个动画值。在某些情况下,动画值需要反转另一个动画值以进行计算。一个例子是反转比例(2x --> 0.5x)

const a = Animated.Value(1);
const b = Animated.divide(1, a);

Animated.spring(a, {
toValue: 2,
}).start();

b 将跟随 a 的弹性动画并生成 1 / a 的值。

基本用法如下

<Animated.View style={{transform: [{scale: a}]}}>
<Animated.Image style={{transform: [{scale: b}]}} />
<Animated.View>

在此示例中,内部图像根本不会被拉伸,因为父级的缩放被抵消了。如果您想了解更多信息,请查看 动画指南

深色状态栏

StatusBar中新增了一个barStyle值:dark-content。通过此添加,您现在可以在 Android 和 iOS 上都使用barStyle。行为现在将如下所示

  • default:使用平台默认值(iOS 上为浅色,Android 上为深色)。
  • light-content:使用带有黑色文本和图标的浅色状态栏。
  • dark-content:使用带有白色文本和图标的深色状态栏。

...以及更多

以上只是 0.36 版本变化的一个示例。请查看GitHub 上的发布说明,查看新功能、错误修复和破坏性更改的完整列表。

您可以通过在终端中运行以下命令升级到 0.36

$ npm install --save react-native@0.36
$ react-native upgrade

React Native 应用的从右到左布局支持

·阅读约8分钟
Mengjue (Mandy) Wang
Facebook 软件工程师实习生

将应用发布到应用商店后,国际化是扩大受众的下一步。全球有 20 多个国家和许多人使用从右到左 (RTL) 语言。因此,让您的应用支持 RTL 对他们来说是必要的。

我们很高兴地宣布,React Native 已经改进以支持 RTL 布局。这现在可以在react-native主分支中获得,并将可以在下一个 RC 中获得:v0.33.0-rc

这涉及更改 RN 使用的核心布局引擎 css-layout、RN 核心实现以及特定的开源 JS 组件以支持 RTL。

为了在生产环境中对 RTL 支持进行实战测试,最新版本的 **Facebook Ads Manager** 应用程序(第一个跨平台 100% RN 应用程序)现在提供阿拉伯语和希伯来语版本,并在 iOSAndroid 上支持 RTL 布局。以下是它在这些 RTL 语言中的外观:

RN 对 RTL 支持的总体变更

css-layout已经有startend的布局概念。在从左到右 (LTR) 布局中,start表示leftend表示right。但在 RTL 中,start表示rightend表示left。这意味着我们可以让 RN 依赖于startend计算来计算正确的布局,其中包括positionpaddingmargin

此外,css-layout已经使每个组件的方向都继承自其父级。这意味着,我们只需将根组件的方向设置为 RTL,整个应用程序就会翻转。

下图描述了高层次的变化:

这包括:

通过此更新,当您允许您的应用程序使用 RTL 布局时:

  • 每个组件布局将水平翻转
  • 如果您使用支持 RTL 的开源组件,某些手势和动画将自动具有 RTL 布局
  • 可能需要最少的额外努力才能使您的应用程序完全支持 RTL

让应用程序支持 RTL

  1. 为了支持 RTL,您应该首先将 RTL 语言包添加到您的应用程序中。

  2. 通过在原生代码开头调用 allowRTL() 函数来允许您的应用程序使用 RTL 布局。我们提供了此实用程序,以便仅在您的应用程序准备就绪时才应用 RTL 布局。这是一个示例:

    iOS

    // in AppDelegate.m
    [[RCTI18nUtil sharedInstance] allowRTL:YES];

    安卓

    // in MainActivity.java
    I18nUtil sharedI18nUtilInstance = I18nUtil.getInstance();
    sharedI18nUtilInstance.allowRTL(context, true);
  3. 对于 Android,您需要将android:supportsRtl="true"添加到AndroidManifest.xml文件中的<application>元素中。

现在,当您重新编译应用程序并将设备语言更改为 RTL 语言(例如阿拉伯语或希伯来语)时,您的应用程序布局应该会自动更改为 RTL。

编写支持 RTL 的组件

通常,大多数组件已经支持 RTL,例如:

  • 从左到右布局
  • 从右到左布局

然而,有几种情况需要注意,您将需要I18nManager。在I18nManager中,有一个常量isRTL来判断应用程序的布局是否为 RTL,以便您可以根据布局进行必要的更改。

具有方向含义的图标

如果您的组件有图标或图像,它们在 LTR 和 RTL 布局中的显示方式将相同,因为 RN 不会翻转您的源图像。因此,您应该根据布局样式翻转它们。

  • 从左到右布局
  • 从右到左布局

以下是根据方向翻转图标的两种方法:

  • 给图像组件添加 transform 样式

    <Image
    source={...}
    style={{transform: [{scaleX: I18nManager.isRTL ? -1 : 1}]}}
    />
  • 或者,根据方向改变图像源

    let imageSource = require('./back.png');
    if (I18nManager.isRTL) {
    imageSource = require('./forward.png');
    }
    return <Image source={imageSource} />;

手势和动画

在 Android 和 iOS 开发中,当您切换到 RTL 布局时,手势和动画与 LTR 布局相反。目前,在 RN 中,手势和动画在 RN 核心代码级别不受支持,而是在组件级别。好消息是,其中一些组件今天已经支持 RTL,例如SwipeableRowNavigationExperimental。但是,其他带有手势的组件将需要手动支持 RTL。

一个很好的说明手势 RTL 支持的例子是SwipeableRow

手势示例
// SwipeableRow.js
_isSwipingExcessivelyRightFromClosedPosition(gestureState: Object): boolean {
// ...
const gestureStateDx = IS_RTL ? -gestureState.dx : gestureState.dx;
return (
this._isSwipingRightFromClosed(gestureState) &&
gestureStateDx > RIGHT_SWIPE_THRESHOLD
);
},
动画示例
// SwipeableRow.js
_animateBounceBack(duration: number): void {
// ...
const swipeBounceBackDistance = IS_RTL ?
-RIGHT_SWIPE_BOUNCE_BACK_DISTANCE :
RIGHT_SWIPE_BOUNCE_BACK_DISTANCE;
this._animateTo(
-swipeBounceBackDistance,
duration,
this._animateToClosedPositionDuringBounce,
);
},

维护支持 RTL 的应用程序

即使在发布了最初兼容 RTL 的应用程序后,您仍然可能需要迭代新功能。为了提高开发效率,I18nManager提供了forceRTL()函数,以便更快地进行 RTL 测试,而无需更改测试设备语言。您可能希望在应用程序中为此提供一个简单的开关。以下是 RNTester 中 RTL 示例的一个例子:

<RNTesterBlock title={'Quickly Test RTL Layout'}>
<View style={styles.flexDirectionRow}>
<Text style={styles.switchRowTextView}>forceRTL</Text>
<View style={styles.switchRowSwitchView}>
<Switch
onValueChange={this._onDirectionChange}
style={styles.rightAlignStyle}
value={this.state.isRTL}
/>
</View>
</View>
</RNTesterBlock>;

_onDirectionChange = () => {
I18nManager.forceRTL(!this.state.isRTL);
this.setState({isRTL: !this.state.isRTL});
Alert.alert(
'Reload this page',
'Please reload this page to change the UI direction! ' +
'All examples in this app will be affected. ' +
'Check them out to see what they look like in RTL layout.',
);
};

在处理新功能时,您可以轻松切换此按钮并重新加载应用程序以查看 RTL 布局。好处是您无需更改语言设置即可进行测试,但某些文本对齐不会更改,如下一节所述。因此,在发布之前,始终建议在 RTL 语言中测试您的应用程序。

限制和未来计划

RTL 支持应该涵盖您应用程序中的大部分用户体验;但是,目前存在一些限制:

  • 文本对齐行为在 Android 和 iOS 中有所不同
    • 在 iOS 中,默认文本对齐取决于活动语言包,它们始终位于一侧。在 Android 中,默认文本对齐取决于文本内容的语言,即英语将左对齐,阿拉伯语将右对齐。
    • 理论上,这应该在跨平台保持一致,但有些人在使用应用程序时可能更喜欢一种行为而不是另一种。可能需要更多的用户体验研究来找出文本对齐的最佳实践。
  • 没有“真正的”左/右

    如前所述,我们将 JS 端的left/right样式映射到start/end,代码中所有 RTL 布局的left在屏幕上变为“right”,代码中的right在屏幕上变为“left”。这很方便,因为您无需过多更改产品代码,但这也意味着无法在代码中指定“真正的左”或“真正的右”。将来,允许组件独立于语言控制其方向可能很有必要。

  • 使手势和动画的 RTL 支持更具开发人员友好性

    目前,使手势和动画与 RTL 兼容仍需要一些编程工作。未来,理想的做法是找到一种方法,使手势和动画的 RTL 支持更具开发人员友好性。

试一试!

请查看RNTester中的RTLExample,了解有关 RTL 支持的更多信息,并告诉我们它的效果如何!

最后,感谢您的阅读!我们希望 React Native 对 RTL 的支持能帮助您为国际受众发展您的应用程序!