React Native 移动开发入门与实战

文章推荐
Selenium 自动化测试从零实战【阅读原文】
原来这样做,才能向架构师靠近【阅读原文】
Cordova App 打包全揭秘【阅读原文】
TensorFlow on Android:物体识别【阅读原文】
TensorFlow on Android:训练模式【阅读原文】
图解敏捷教练和 ScrumMaster【阅读原文】
[运营专题]零预算引爆个人和企业品牌【阅读原文】
一学就会的 WordPress 实战课【阅读原文】
快速学习 Spring Boot 技术栈【阅读原文】
Webpack 达人的成长之路【阅读原文】
如何从生活中领悟设计模式【阅读原文】

这里写图片描述

作者简介

李焱,2010 年毕业于北京大学信息管理系,获得信息管理学士学位及计算机软件学士学位;毕业后曾在酷我音乐、人人游戏、腾讯等互联网公司从事 Web 开发及游戏开发;2015 年先后担任成都鱼说科技大前端技术总监和 CTO 职位;2017 年初创办漫极客科技,致力于人工智能产品开发。

课程简介

本课程主要讲解 React 的基础知识及应用案例,包括 props、state、生命周期函数等,样式和 Flex 布局,React Native 内置的 API 和 UI 组件介绍、路由、状态管理 Redux,如何使用第三方组件,JS 跟原生代码之间的通信、动画、调试、静态代码检查 Flow,测试 Jest & Enzyme,打包发布和热更新等。

学完本课程可以系统掌握 React Native 生态系统各个方面的知识,可以完全从零开始开发并发布上线一款能同时运行在 iOS 和 Android 上的 APP,进入 APP 开发之路,向大前端职业生涯迈进一大步。

实践案例包括:

一个简单的图片搜索引擎
一个阅读类 APP
一个神秘的 APP

导读:课程概要

我使用 React / React Native + Redux + Webpack 开发两年有余,期间既有发现新功能时的惊喜,也有踩坑时的痛苦,希望将这两年多的所学所思整理成文,让新人少踩坑,早入 React / React Native 之门。

内容主要包括:

  • React 基础知识,包括 JSX、Component、props vs state、生命周期函数等;
  • React Native 提供的基础 API 和 UI 组件;
  • React Native 里如何组件化地写样式;
  • router;
  • 状态管理之 Redux;
  • 如何使用第三方组件;
  • JS 与原生代码之间的通信;
  • 动画;
  • 静态代码检查;
  • 测试;
  • 打包发布上架 APPStore;
  • 热更新。

最后会安排一个完整的实战项目,覆盖 React Native 绝大部分知识点。课程代码会放在 GitHub 上。

关于作者

李焱,网名 magicly,2010 年毕业于北大,获信息管理学士学位和计算机软件学士学位,毕业之后先后在酷我音乐、人人游戏、腾讯等知名互联网公司工作,之后在成都一家创业公司担任 CTO,带过一年多的团队。

从前到后,网页设计、微信公众号、小程序、后端接口、数据库设计优化、运维脚本、数据统计分析等都做过,使用过 Java、JS、Scala、Python、Bash、C++、Rust 等语言,算不上精通,但也远非只会写 Hello World。

最近关注大前端(如 Node.js、React、React Native、Electron、Web Workers、WebAssembly、WebGL、Three.js、D3.js 等)及 深度学习算法 等。

欢迎关注 微博,或者扫描关注微信公众号:

这里写图片描述

适用于什么人群

  • 有一定 Web 前端基础,会使用 React 开发 WebAPP,想开发原生 APP 的 Web 前端开发人员。
  • 做 Android 想要顺便做一下 iOS 的开发人员, 反之亦然。
  • Android 和 iOS 都会做,想使用 React Native 来提高自己的竞争力和开发效率的人员。
    如果既不会 React,又没做过原生 APP 开发,建议先看 React课程。

为什么是 React Native

React Native 开源以来受到了极大的关注,目前 GitHub 上有 55000+ stars,背后有 Facebook 这样的大公司支持,也做出了有大量用户的产品,国内也有很多公司在用。开发效率高于原生应用,运行效率高于 H5 混合式开发,综合素质很强。

阿里也开源了一款类似的框架 Weex。 但是 Weex 开源时间短,用的人还比较少,整体上没有 RN 成熟。

React Native 还能用么

为什么会问这个问题呢?因为今年发生了两件“大事”,很多人都说不要用 React / React Native 了。那我们先来看看这两件“大事”到底对 React / React Native 生态系统有什么影响,是否还可以继续使用 React / React Native 呢。

苹果下架热更新应用

今年三月份,很多 iOS 开发者收到了苹果的警告邮件,说他们的 APP 里使用了“热更新”技术,需要在下一个版本里面去掉,否则会面临被下架 APPStore 的风险。

enter image description here

然后 6 月份的时候,苹果动了真格,在中国区下架了将近 3 万款 APP。一时间各大社区都炸了锅,包括 JSPatch、React Native、Weex、Cocos2d-x、白鹭引擎等都在讨论自己是不是中枪了。结果发现,真正受影响的是 JSPatch、RolloutIO,而 React Native 等并没有受到影响,而有部分开发者的 React Native APP 被下架最后发现也是因为用了某些第三方库,而这些库用到了 JSPatch。为什么都是热更新,差别这么大呢?

先来看看什么是“热更新”。下面的图(或者类似的图)大家应该见过吧。

enter image description here

enter image description here

简单来说,热更新就是 APP 在通过 APPStore 下载后,在启动的时候去检查是否有版本更新,有的话直接下载更新资源,绕过 APPStore 而达到直接更新 APP 的方式。这样做有什么好处呢,当然:

  • 加快发布速度,可以快速修复 bug,或者做一些运营活动。
  • 更新包很小,便于用户快速升级。更新包可能 1M,而整个 APP 可能 100M。
  • 强制要求用户升级。
  • 等苹果审核过后,通过热更新增加“功能”,如微信或支付宝支付,这样可以避免被苹果抽成 30%。
  • 都是热更新,JSPatch 为什么就被下架了,而 React Native 等还好好的。

简单来说就是 JSPatch 对所有 Objective-C 的 API 进行了映射,允许开发者在 JS 端调用任意原生代码,这就很危险啦。万一热更新服务器被黑客攻击,或者更新的时候受到中间人攻击,那基本上黑客就可以使用任意的系统接口了。同样这样也绕过了苹果的审核,完全可以在发布之后把 APP 通过热更新换成另外一个 APP。

而给开发者的警告邮件里面也提到了:

这包括将任意参数传递给诸如 dlopen()、dlsym()、respondingToSelector:、performSelector:、method_exchangeImplementations()等动态方法的代码,以及运行远程脚本以便更改应用程序行为或基于下载的脚本调用 SPI。即使远程资源不是故意恶意的,它也可能很容易地通过中间人(MiTM)攻击被劫持,这可能对应用程序的用户造成严重的安全隐患。

而 React Native、Weex 等都没有这些问题,甚至苹果的开发者协议 3.3.2 节明确写明了是可以下载 JS 执行的。

解释型的代码只能运行在所有脚本、代码和解释器打包在应用内且不是下载的情况下。唯一的例外是前述代码是脚本,且代码下载后运行于苹果内置的 WebKit 或 JavascriptCore 框架内,且该脚本和代码不改变应用提供的主要特性和功能以及主要目的,并与 App Store 上提交的应用描述一致。

可见,苹果仍然允许运行于 WebKit 之上的 HTML 5 页面以及运行于 JavascriptCore 之上的诸如 React Native 和 Weex 之类的 JS-Native 框架,无论这些页面和 JS 文件是远程的还是本地的。

另外,去年用 React Native 开发的一块 App 一直以来也没有受到影响,现在还在 APPStore 里面。当然如果大家还是有所担心的话,可以不用 React Native 去做热更新。 除了热更新,React Native 还有很多值得学习使用的地方,能大大提高开发效率。

若想要了解更多细节,读者可查看其他资料。

React 开源协议

前段时间 React 的开源协议问题被大肆报道,百度发文要求全面停止使用 React,Apache 也要求所有旗下项目禁止使用带 Facebook BSD+PATENTS License 的项目,并限时完成替代。不过后来 Facebook 终于顶不住压力更换了许可证:

https://www.infoq.com/news/2017/09/facebook-react-mit

其实呢,这件事看看热闹就可以了,同时可以顺便了解一下开源许可证及一些专利知识。

Hello React Native

我们还是“俗”一下,来一个 ReactNative 版本的 Hello World 吧。

首先需要安装 Node.js,如果还没有安装的请访问 Node.js,下载对应平台版本。 Mac 下直接安装后面好像会遇到权限的问题,建议使用 https://github.com/creationix/nvm 安装。 安装好 Node 后,建议安装 nrm,然后选择访问速度最快的 npm registry,这样能为后面安装第三方 npm 包节省大量时间。

开发 Native 应用,需要配置比较多的环境。比如开发 iOS,需要有 Mac 电脑,然后安装 XCode; 如果是开发 Android,需要安装 JDK、Android Studio 等,整个配置非常费时。好在今年3月份,Facebook 开发了 Create React Native App, 这个工具是仿照 Create React App 设计的,大大简化了初始开发流程,只需要如下:

npm install -g create-react-native-app
create-react-native-app HelloReactNative

cd HelloReactNative
npm start

然后这个工具会在你的电脑上启动一个 server,并且打印出一个二维码。你只需要在手机上装 Expo APP(iOS 和 Android 都可以),让手机和电脑在同一个 WiFi 下,然后打开 Expo APP。
这里写图片描述
Expo
扫描二维码,即可在手机上看到第一个 React Native APP。 然后修改 App.js 文件,保存后手机上就能立马看到效果!多亏了热更新!

const App = () => {
  return <View style={styles.container}>
    <Text>Hello React Native!</Text>
  </View>
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
});

export default App;

Hello React Native
摇一摇手机,会出现菜单。
这里写图片描述

第01课:React 基础知识

React Native 大部分的知识点还是 React 那一套,包括 JSX、Props、State、Lifecycle 等。 如果会 React 的话,可以说 React Native 已经会了一大半了,毕竟 React 的口号是:

Lear n Once, Write Anywhere

React 简介

在几年前开发 Web 页面的标准思路还是 HTML 写内容、CSS 控制样式、JavaScript 控制逻辑和动态效果,三者要分开,目录结构也是按文件类型分开的。然而随着 Web 基础的飞速发展,浏览器性能越来越好,支持的功能越来越多,前端工程化越来越强,Web 开发已经从原来的写页面变成了开发 Web APP!以前按照文件类型组织项目的方式也已经越来越满足不了现在复杂的需求了。

随着 React、Ember、Angular 2 等框架的出现以及 W3C 本身也提出的 web components 技术,Web 开发已经进入了组件化的开发时代,思路、工具以及开发方式等都要有重大转变,才能适应目前日益复杂和快速多变的 Web 开发工作。

2013 年 Facebook 在 JSConfUS 大会上宣布开源 React,React 提出的 Virtual DOM 技术以及组件化开发思想大大方便开发大型 Web 应用,开源后迅速走红,得到广大开发者的喜爱,逐渐形成了完善的 React 生态圈。截止目前(2017-11-08 13:57),React 在 github 上有 80446 个 stars
下面是我两年多学习使用 React 的经验总结,希望能对大家有所帮助。

  • 函数就是组件,组件就是函数;
  • 每一个组件只做一件事;
  • 显示组件和逻辑组件分开。

接下来的章节会反复强调这几个基本思路。

开始学习 React 之前,强烈建议先看一下这篇关于 React 开发思想的文章,思想上转化过来,后面会事半功倍。

下面简单介绍一下 React 的基础知识,如 JSX、Stateless Component、Props、State、Lifecycle 和事件等,了解了这些,就可以进入 React Native 的开发了。

没有用过 React 的读者建议可以先看看我的另一门 React学习课程 里的 30分钟入门介绍,然后再学习下面的知识点细节。

JSX

JSX 即 JavaScript Extension,是在 JavaScript 语言上做的扩展,是为了在 JavaScript 里面更方便地书写 HTML 标签等而开发。JSX 不同于其他框架采用的模板,它本质上就是 Javascript,所以功能跟 JavaScript 一样完整,远比其他任何模板语言都灵活方便,而且也不用单独学一门模板语言。

以下即是最简单的一段 JSX 代码。

const hi = Hello, JSX!

嵌入 JS 表达式

JSX 里面可以很容易地嵌入 JS 表达式,比如:

const img = <img src={user.photo} alt="this is a photo." />

而 JSX 本身也是一个表达式,会被 Babel 等编译成普通的 JS 对象,所以任何可以使用对象的地方都可以使用 JSX,比如 if 判断、for 循环里面、赋值给变量,作为函数参数传递或者返回值返回。比如:

function getGreeting(user) {
  if (user) {
    return <h1>Hello, {formatName(user)}!</h1>;
  }
  return <h1>Hello, Stranger.</h1>;
}

指定属性

JSX跟 HTML 很像(其实就是为了方便使用 HTML 才发明了 JSX),所以支持指定属性。比如:

const img = <img src={user.photo} alt="this is a photo." />

注意,不要使用引号把 {} 包起来.

子元素

JSX 可以包含子元素(就跟 HTML 标签一样),如果没有子元素可以直接用 来结束标签。

const element = (
  <div>
    <h1 className="hi">Hello!</h1>
    <h2>Good to see you here.</h2>
  </div>
);

注意 JSX 其实是 JS,所以各种命名等都习惯用 JS 常用的 camelCase 风格,并且由于 class 是 JS 的关键字,所以标签里面要用 className。

JSX 的内部表示

Babel 会把 JSX 编译成React.createElement()调用,如下两者是完全等同的:

const element = (
  <h1 className="greeting">
    Hello, world!
  </h1>
);
const element = React.createElement(
  'h1',
  {className: 'greeting'},
  'Hello, world!'
);

React.createElement() 会做一些检查(如 props 类型等)来减少 bug,然后生产如下的对象:

// Note: this structure is simplified
const element = {
  type: 'h1',
  props: {
    className: 'greeting',
    children: 'Hello, world'
  }
};

这些对象叫 “React elements”,可以把它看成是对想要展现在屏幕上的内容描述。React 读取这些对象,然后用它们构造 DOM 节点,并保持状态一致。

如果没有子元素的话,可以直接使用闭标签, 比如:

<img src="x.jpg" alt="a pic" />

会被翻译成:

React.createElement(
  'img',
  {src: 'x.jpg', alt: 'a pic'},
  null
)

你可以使用 在线 Babel 编译器 来查看 JSX 具体编译成什么样的 JS。

必须 import React

因为 JSX 是编译成React.createElement,所以 React 必须在 JSX 文件中被 import,否则会编译报错。比如下面的例子,虽然没有直接引用 React,但是也需要 import 进来。这是初学者常犯的一个错误,经常出现在 stateless component文件中, 因为container component 里面需要显示extends React.Component

import React from 'React';
import CustomButton from './CustomButton';

function WarningButton() {
  // return React.createElement(CustomButton, {color: 'red'}, null);
  return <CustomButton color="red" />;
}

自定义的组件必须大写开头

小写字母开头的组件是 built-in 的组件,如 <div> 或者 <span>等。如果定义了一个小写字母开头的组件,那也可以, 不过在用在 JSX 之前,必须赋值给一个大写字母开头的变量。感觉有点奇怪, 所以不建议这样用,记住定义组件的时候要有大写字母开头就可以了。

import React from 'react';

// Wrong! This is a component and should have been capitalized:
function hello(props) {
  // Correct! This use of <div> is legitimate because div is a valid HTML tag:
  return <div>Hello {props.toWhat}</div>;
}

function HelloWorld() {
  // Wrong! React thinks <hello /> is an HTML tag because it's not capitalized:
  return <hello toWhat="World" />;

  // 可以这样修复
  //const Hello = hello;
  //return <Hello toWhat="World" />;
}

最好是定义的时候就定义成大写字母开头的:

import React from 'react';

// Correct! This is a component and should be capitalized:
function Hello(props) {
  // Correct! This use of <div> is legitimate because div is a valid HTML tag:
  return <div>Hello {props.toWhat}</div>;
}

function HelloWorld() {
  // Correct! React knows <Hello /> is a component because it's capitalized.
  return <Hello toWhat="World" />;
}

JSX 的 props

JSX 中 props 的值可以是很多种类型。

JS 表达式

<MyComponent foo={1 + 2 + 3 + 4} />

字符串

下面两种写法是一样的, 当然我们一般用第一种,因为简单啊!

<MyComponent message="hello world" />

<MyComponent message={'hello world'} />

不指定值默认是true

下面两者等价,不过官方说不推荐第一种,因为容易与 ES6 object shorthand 的写法{autocomplete}表达的是 {autocomplete: autocomplete}混淆,官方提供这种写法只是为了跟 HTML 保持一致。

<MyTextBox autocomplete />

<MyTextBox autocomplete={true} />

展开 object

如果父组件传递的 props 想全部传递给子组件,用 … 展开 object 操作符会很方便,这个也是 ES6 的新特性 Spread syntax。

function App1(props) {
  return <Greeting firstName={props.firstName} lastName={props.lastName} />;
}

function App2(props) {
  return <Greeting {...props} />;
}

<App1 props={firstName: 'Ben', lastName: 'Hector'} />
<App2 props={firstName: 'Ben', lastName: 'Hector'} />

不过这个功能要慎用,我见过很多人不管父组件传递过来的 props 包含多少个属性,统统都直接{...props} 传递给子组件,而其实父组件传递过来的props可能有 10 个属性,而子组件只需要 1 个。而且这样写也看不出来子组件具体需要那几个参数,所以如果不是特别多的话,最好还是显示地写出来传递的props 值。记住:

Explicit is better than implicit.。

子组件

JSX 中在开标签和闭标签之间的内容会通过 props.children 传递给组件,值的类型有很多种。

字符串

<ComponentA>Hello, React!</ComponentA>

上述写法 ComponentA中可以通过props.children 拿到字符串 Hello, React!。字符串是unescaped 的,所以<>需要转义之后传递 &lt;&gt;。JSX 会把下面这些空白去掉:

  • 每行开始和结尾的空白。
  • 空行会去掉。
  • 挨着标签的换行会去掉。
  • 字符串中间的很多换行会合并成一个空格。
  • 字符串中间的空格不会去除,不过 HTML 显示的时候本身多个空格只会显示一个。

所以下面这几种都是一样的, 大家可以合理利用空白来增强代码的阅读性。

    <div>Hello World</div>

    <div>
      Hello            World
    </div>

    <div>
      Hello


      World
    </div>

    <div>

      Hello World
    </div>

JSX组件

跟 HTML 一样,JSX 组件可以嵌套,不同种类的子组件也可以混用,比如:

<MyContainer>
  Here is a list:
  <ul>
    <li>Item 1</li>
    <li>Item 2</li>
  </ul>
  <ComponentA>sth...</ComponentA>
  <ComponentB />
</MyContainer>

JS表达式

比如:

<MyComponent>foo</MyComponent>

<MyComponent>{'foo'}</MyComponent>

<MyComponent>{1 + 2 * 3}</MyComponent>

这种方式用来循环渲染列表的时候很适合,如:

function Item(props) {
  return <li>{props.message}</li>;
}

function TodoList() {
  const todos = ['finish doc', 'submit pr', 'nag dan to review'];
  return (
    <ul>
      {todos.map((message) => <Item key={message} message={message} />)}
    </ul>
  );
}

函数

JS 里面函数是第一等公民,也是可以像数字、字符串一样传递的。当然传递一个函数拿来显示会很奇怪(试一下,发现不会渲染,除非调用 toString() 转化为String),所以一般传递过去的函数会被调用进行一些转换,转化为 React 可以渲染的东西。

// Calls the children callback numTimes to produce a repeated component
function Repeat(props) {
  let items = [];
  for (let i = 0; i < props.numTimes; i++) {
    items.push(props.children(i));
  }
  return <div>{items}</div>;
}

function ListOfTenThings() {
  return (
    <Repeat numTimes={10}>
      {(index) => <div key={index}>This is item {index} in the list</div>}
    </Repeat>
  );
}

Booleans、Null、Undefined 会被忽略

falsenullundefined 以及true 都是合法的子元素,但是不会被渲染出来。下面这些 JSX 渲染出来对的都是一样的: 空元素!

<div />

<div></div>

<div>{false}</div>

<div>{null}</div>

<div>{undefined}</div>

<div>{true}</div>

这样做条件渲染就比较方便了,下面这样写的话只有 showHeadertrue 的时候<Header />才会被渲染:

<div>
  {showHeader && <Header />}
  <Content />
</div>

注意有些所谓的 falsy value,比如 0 是会被 React 渲染的。所以下面的例子中,如果 messages 是空的话,是会显示 0 的。

<div>
  {props.messages.length &&
    <MessageList messages={props.messages} />
  }
</div>

可以把&& 之前的表达式变成boolean 来修复:

<div>
  {props.messages.length > 0 &&
    <MessageList messages={props.messages} />
  }
</div>

如果确实需要显示 falsenullundefined 以及 true 的话,需要转化为String

value is {String(true)}
value is {'' + null}
value is {`${undefined}`}

具体可参考如下资料:

Stateless Component

组件化开发思想第一条是:函数就是组件,组件就是函数。要自定义组件很简单,直接定义函数就可以了。

import React from 'react';

const ComponentA = (props) => {
  return <h1>ComponentA, welcome {props.name}</h1>
}
const App = () => {
  const componentA1 = <ComponentA name="magicly" />
  const componentA2 = ComponentA({ name: "magicly" });
  console.log(typeof componentA1, typeof componentA2, typeof ComponentA);
  console.log(componentA1, componentA2);
  return (
    <div className="App">
      <componentA1 />
      {componentA1}
      {componentA2}
    </div>
  );
}

export default App;

可以看到ComponentA是函数,所以ComponentA({ name: "magicly" }) 是函数调用,所以componentA1其实就是函数调用的结果,类型是 object。而 <ComponentA name="magicly" />其实也是调用了函数,所以两种写法结果是一样的。

但是我把 componentA1componentA2 打印出来,发现两个 object 并不完全一致:

componentA1的typefunction ComponentA(props)
componentA2的type"h1"

而实际渲染出来的 DOM 结构则完全一样。

还要注意,因为componentA1componentA2 已经是 React 组件实例了,所以再写 <componentA1 /><componentA2 /> 没有意义,相当于是在一个 object 上做函数调用,是会报错的,不过 React 没有报错,只是什么都没有输出而已。而如果你直接写{componentA1()}则会得到报错TypeError: componentA1 is not a function,从这里可以看出 <ComponentA />这种方式确实不完全等同于 ComponentA()

当然,按照习惯,我们还是用 JSX 的语法,写成 HTML 标签的形式:

<ComponentA name="magicly" />

Stateless Component vs Class Component

React 里面有两种定义 Component 的方式,除了前面说的函数式定义方式,另外一种是基于 class 的:

class ComponentB extends React.Component {
  render() {
    return <h1>ComponentB, welcome {this.name}</h1>
  }
}

那应该选哪种方式呢?每个人都有自己的 taste,但我的建议是:优先使用 Stateless Component! stateless 顾名思义就是没有 state,实际上用函数定义的 component 除了没有 state,还没有任何 react 组件的生命周期方法,也没有 this!就是一存粹天真的函数,如此而已。所以简单直接,代码还可以少敲几行!

best practice 是:

用 Stateless 组件定义一堆小组件并包含样式。然后在要用到 state 或者生命周期函数的时候再用 class 组件包一层,只写逻辑,而把渲染样式等转发给 stateless 组件。

如果你用 redux 等来管理状态了,那么理论上而言,你是不会需要写这些包装组件的,因为 react-redux 自己会包装一层,也就是说再也不需要 class 定义的组件了。

注意,我说的是如果你用 redux 的话应该是不用 class 组件的,我并没有说为了不用 class 组件,你要用 redux 哈!

下次再看到 class 定义的组件, 请考虑一下重构成 stateless component,如果你觉得实在没法重构,请出门散散步,回来继续思考!

具体可参考资料:https://facebook.github.io/react/docs/components-and-props.html

State & Lifecycle

组件化开发思想第二条是:样式跟状态分离。现在我们来实现一个时钟应用:只是显示当前时间!

显示

用 stateless 组件,so easy:

const Clock = (props) => {
  return <h1>{new Date().toLocaleTimeString()}</h1>
}

state

stateless 组件和 props 都是静态的,只能显示出固定的内容,如果我们要显示动态内容(每一秒的时间都不一样呢),需要用到 state。所以,我们转化为 class 组件。

class Clock2 extends React.Component {
  constructor(props) {
    super(props);// constructor 里第一行必须是这行,否则会报错
    this.state = {
      time: new Date().toLocaleTimeString(), // 这里是唯一一处直接复制给 this.state 的,其他地方请用 this.setState({...})
    }
  }
  render() {
    return <h1>{this.state.time}</h1>
  }
}

添加定时器

然而上述代码还是静态的,只能显示 constructor 执行那一秒时的时间,我们需要用 setInterval 起一个定时器,每秒去更新时间。

在哪里起定时器呢? 应该是在 DOM 节点被渲染的时候,这个叫 Mounting。 同样,在不需要 Clock 组件的时候,我们也需要清除定时器,否则会出现内存泄露。那应该在哪里清除呢?应该在 DOM 节点被卸载的时候, 叫 Unmounting。

React 提供了这两个时间点的回调函数给我们用。

class Clock2 extends React.Component {
  constructor(props) {
    super(props);// constructor 里第一行必须是这行,否则会报错
    this.state = {
      time: new Date().toLocaleTimeString(), // 这里是唯一一处直接复制给 this.state 的,其他地方请用 this.setState({...})
    }
  }
  componentDidMount() {
    console.log('componentDidMount');
    // timer 不需要拿来渲染, 所以不要把 timer 让在 this.state 里
    this.timer = setInterval(() => {
      this.setState({
        time: new Date().toLocaleTimeString(),
      }) // 注意,如果直接修改 this.state.time = new Date().toLocaleTimeString() 是不行的, 不会渲染页面!
    })
  }
  componentWillUnmount() {
    console.log('componentDidMount');
    clearInterval(this.timer);
  }
  render() {
    return <h1>{this.state.time}</h1>
  }
}

一定要注意代码里面的几条注释!

重构:显示和状态分离

我们之前说了,用 stateless 组件来负责显示,class 组件来负责状态和逻辑,代码可以改为:

import React from 'react';

const Clock = (props) => {
  return <h1>{props.time}</h1>
}

class ClockContainer extends React.Component {
  constructor(props) {
    super(props);// constructor 里第一行必须是这行,否则会报错
    this.state = {
      time: new Date().toLocaleTimeString(), // 这里是唯一一处直接复制给 this.state 的,其他地方请用 this.setState({...})
    }
  }
  componentDidMount() {
    console.log('componentDidMount');
    // timer 不需要拿来渲染, 所以不要把 timer 让在 this.state 里
    this.timer = setInterval(() => {
      this.setState({
        time: new Date().toLocaleTimeString(),
      }) // 注意,如果直接修改 this.state.time = new Date().toLocaleTimeString() 是不行的, 不会渲染页面!
    })
  }
  componentWillUnmount() {
    console.log('componentDidMount');
    clearInterval(this.timer);
  }
  render() {
    return <Clock time={this.state.time} />
  }
}
export default ClockContainer;

这样我们就把负责静态内容显示的组件拆为 Clock,而用 ClockContainer专门负责逻辑:每秒更新一次状态。

肯定会有人说,这完全就是多此一举把代码搞复杂了啊。 是的!在这个简单的例子里面确实没必要,但是如果在实战中,可能 Clock 这个负责显示的组件会很复杂,有颜色、有布局、有不同的形状吗,甚至可能还会调用很多其他更小的组件,这样拆分后在修改Clock 的时候就完全不用管ClockContainer。 同样,如果有一天你想把显示逻辑改为精确到毫秒,你也只用修改 ClockContainer 而完全不用动Clock

当然,如果你的实际项目就是像 demo 这么简单的话,我觉得你应该使用 jQuery 甚至直接用原生 JS 而不用 React 的啊! I really mean it!

具体可参考如下资料:

在 React 里面处理事件,跟在 HTML DOM 节点上很像,区别有两点:

  • React 里用 camelCase 而不是 lowercase;
  • JSX 里传递函数,而不是 string。

比如,HTML 里这样写:

<button onclick="clickme()">
  Click me!
</button>

在 React 里要这样写:

<button onClick={clickme}>
  Click me!
</button>

注意上面 clickme 后面没有括号哦,react 里面是传递函数本身,如果加了()就变成了函数调用咯。

另外,在 HTML 里一般可以用return false 的方式来阻止默认行为,在 React 里面不行,必须用 preventDefault

HTML 里:

<a href="#" onclick="console.log('clicked'); return false">
  Click me!
</a>

React 里:

const handleClick = e => {
  e.preventDefault();
  console.log('clicked');
}
<a href="#" onClick={handleClick}>
  Click me!
</a>

其中 e 是 React 封装的 SyntheticEvent,所以可以不用考虑浏览器兼容性问题。 God bless!

this 问题

我们在 30分钟简介 中提到过这个问题,再来看看代码。

class Toggle extends React.Component {
  constructor(props) {
    super(props);
    this.state = {isToggleOn: true};

    // 如果没有这行,在 button 被 click 然后执行 handleClick 的时候 this 是 undefined
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState(prevState => ({
      isToggleOn: !prevState.isToggleOn
    }));
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.isToggleOn ? 'ON' : 'OFF'}
      </button>
    );
  }
}

所以经常看到很多人写的 constructor 里面一堆 bind:

  this.function1 = this.function1.bind(this);
  this.function2 = this.function2.bind(this);
  this.function3 = this.function3.bind(this);
  ......

解决方法是用 箭头函数,因为 箭头函数 自己没有绑定 this,所以它里面的 this 是自己被定义时候的 this,即组件本身。

箭头函数可以写在 JSX 上,比如:

    return (
      <button onClick={(e) => this.handleClick(e)}>
        {this.state.isToggleOn ? 'ON' : 'OFF'}
      </button>

也可以用 JS 的一个新特性,所谓的 property initializer syntax,如下:

class Toggle extends React.Component {
  constructor(props) {
    super(props);
    this.state = {isToggleOn: true};
  }

  handleClick = () => {
    this.setState(prevState => ({
      isToggleOn: !prevState.isToggleOn
    }));
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.isToggleOn ? 'ON' : 'OFF'}
      </button>
    );
  }
}

总结

这一章我们介绍了 React 的基础知识,有了这些准备,就可以开始 React Native 学习之旅了。 Let’s go!

下一篇

课程内容

导读:课程概要
第01课:React 基础知识
第02课:React Native 基础知识
第03课:样式和布局
第04课:React Native 内置 API 和组件介绍
第05课:导航 Navigation
第06课:使用 Native 代码之 iOS
第07课:使用 Native 代码之 Android
第08课:状态管理 Redux
第09课:动画
第10课:项目实战一
第11课:Debugging
第12课:静态代码检查 Flow
第13课:测试 Jest
第14课:性能分析与优化
第15课:打包发布 & 热更新
第16课:项目实战二
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值