文章目录
引子
软件开发,移动优先;移动开发,RN优先。为什么?RN的性能胜任大部分应用场景,开发效率奇高(前提当然是熟悉web和javascript),热更新,快速见效,出活快,画一个界面三言两语,比原生简洁多了。Flutter? Xamarin? NativeScript? Ionic? Cordova? 我还是选RN。有人担心RN不流行了,会被替代,大可不必,哪种技术能长命百岁?一个产品有三五年的周期就算长寿了。RN短期内死不了。新的JS引擎 Hermes也极大地改进了性能。
经过一段时间的使用,笔者发现:RN代码好写,但小问题不少,一方面是RN本身的问题,另一方面文档不细。
React Native适用场景
大部分应用都可以采用RN,新闻资讯类自不必说,电商类,教育类,视频播放类,也不在话下。CPU密集型?游戏类?这个我没有尝试过。但就像Java和C++/C的关系一样,80~90%都可以由Java搞掂,剩下的C来搞掂。你能吃下90%的业务部分,你还担心什么?
React基础
JSX
JSX 是 React 的核心组成部分,它使用 XML 标记的方式去直接声明界面。JSX貌似丑陋,和JS代码耦合,但其背后的哲学是每个HTML都是由有生命的片段组合而成,有了对片段的良好编程,就能由简到繁,积小成大,去构建更庞大的应用。
JSX的一大好处就是容易组合:
function func1() {
return <View>func1</View>
}
function func2() {
return <View>func2</View>
}
function func3() {
// 多行要用小括号包起来!
return (<View>
{
func1()}
{
func2()}
</View>)
}
这样就比较容易构造复杂的组件。
一个典型的写法:
function doRender() {
return (
<View>
{
list.map(item => (
<Section item={
item} />
))}
</View>
)
}
组件的定义
组件有两种写法:类组件和函数式组件。
类的写法:
class Welcome extends React.Component {
render() {
return <h1>Hello, {
this.props.name}</h1>;
}
}
函数写法:
function Welcome(props) {
const data = [
{
id: 1, name: "John Doe" },
{
id: 2, name: "Victor Wayne" },
{
id: 3, name: "Jane Doe" },
];
return (
<View>
<Text>Hello, {
props.name}</Text>
<View className="users">
{
data.map((user) => (
<Text className="user">{
user}</Text>
))}
</View>
</View>
);
}
函数组件就是可以返回一个ReactElement的函数。
生命周期函数与组件渲染
渲染:通过Javascript API修改DOM对象谓之渲染,如document.write、Node.appendChild或Element.setAttribute等DOM编程接口。
当VDOM更新了,React将其与之前的VDOM快照进行比较,然后仅更新真实DOM中发生更改的内容。如果没有任何改变,真正的DOM根本不会更新。
React DevTools允许我们在Components -> View Settings -> Highlight updates中设置渲染时高亮更新。这将展示所有虚拟渲染。
如果我们想看原生重渲染,我们需要在Chrome DevTools中三个点的菜单-> More tools -> Rendering -> Paint flashing。
每当组件的状态发生变化时,React都会调度一次渲染。调度渲染并不意味着渲染过程会立即发生。 React将尝试为此找到最佳时机去执行这个操作。
通常采用setState函数来渲染组件。
props发生了变化,未必会导致组件渲染。而且,不要直接改变props对象,如this.props.user.name = ‘Felix’;
因为这不会触发任何更改,并且React不会注意到这些更改。
组件的缺省属性
react 提供了语法糖:defaultProps,可以实现属性的混合。
import React from 'react'
// 函数组件
export default function FuncDefault(props) {
console.log(props);//已经完成了混合
return (
<div>
a:{
props.a},b:{
props.b},c:{
props.c}
</div>
)
}
//属性默认值
FuncDefault.defaultProps = {
a: 1,
b: 2,
c: 3
}
// 类组件
export default class ClassDefault extends React.Component {
constructor(props) {
super(props);
console.log(props);
}
}
ClassDefault.defaultProps = {
a:1, b:2, c:3
}
组件之间的通信
- 父组件传属性给子组件,属性变,子组件也跟着变
- 子组件想通知父组件,可以通过调用父组件传递过来的函数,这个函数也是通过props传递的
- 任意两个组件之间通过消息进行通信。
// 发送方
DeviceEventEmitter.emit('sendMsg', {
text:'Hello'});
// 接收方
DeviceEventEmitter.addListener('sendMsg',function(params){
// do something with the params
});
基础API
ReactNode是一个联合类型:
type ReactNode =
| ReactChild
| ReactFragment
| ReactPortal
| boolean
| null
| undefined;
type ReactText = string | number;
type ReactChild = ReactElement | ReactText;
Flex弹性布局
定理:采用Flex加上嵌套能实现任意布局
-
react 宽度基于pt为单位, 可以通过Dimensions 来获取宽高,PixelRatio 获取密度。运行在Android上时,View的长和宽被解释成:100dp 100dp单位是dp,字体被解释成16sp 单位是sp,运行在iOS上时尺寸单位被解释称了pt,这些单位确保了布局在任何不同dpi的手机屏幕上显示不会发生改变
-
基于flex的布局
- view默认宽度为100%
- 水平居中用alignItems, 垂直居中用justifyContent
- 基于flex能够实现现有的网格系统需求,且网格能够各种嵌套无bug
-
图片布局
- 通过Image.resizeMode来适配图片布局,包括contain, cover, stretch
- 默认不设置模式等于cover模式
- contain模式自适应宽高,给出高度值即可
- cover铺满容器,但是会做截取
- stretch铺满容器,拉伸
-
定位
- 定位相对于父元素,父元素不用设置position也行
- padding 设置在Text元素上的时候会存在bug。所有padding变成了marginBottom
-
文本元素
- 文字必须放在Text元素里边
- Text元素可以相互嵌套,且存在样式继承关系
- numberOfLines 需要放在最外层的Text元素上,且虽然截取了文字但是还是会占用空间
例子: Flex布局实现多行多列
容器: flex-wrap: wrap; 允许折行
元素有三种方式:
- flex: 1 0 33.3%;
- flex-basis: 33.3%;
- width: 33.3%;
和Web CSS上FlexBox的不同之处
- flexDirection: React Native中flexDirection:'column’对应Web CSS的flex-direction:‘row’
- alignItems: React Native中alignItems:'stretch’对应Web CSS的align-items:‘flex-start’
- flex: 相比Web CSS的flex接受多参数,如:flex: 2 2 10%;,但在 React Native中flex只接受一个参数
- 不支持属性:align-content,flex-basis,order,flex-basis,flex-flow,flex-grow,flex-shrink
常用UI组件
1、View是最核心、最常用的UI组件,包罗万象。几乎总是和{flex:1}搭配,否则空白。
2、FlatList长列表组件,可以实现下拉刷新和上拉加载。VirtualizedList是FlatList和SectionList的底层实现。FlatList和SectionList都依赖一个数组,每个元素是个字典,SectionList的字典必须有data、title、key。
- 不要在ScrollView里使用FlatList
<View style={
{
flex: 1}} >
<ScollView style={
{
flexGrow: 1}}
nestedScrollEnabled={true}>
<View>
<SafeAreaView>
<ScrollView horizontal
style={
{
width: '100%', height: 'your_height'}}>
{data.map((item, index) => (
<View key={index}>
// Your component
</View>
))}
</ScrollView> -- Horizontal Scroll --
</SafeAreaView>
</View>
</ScrollView>
</View>
3、WebView,显示html文档。
4、导航器
5、富文本编辑器: wxik/react-native-rich-editor和https://github.com/imnapo/react-native-cn-richtext-editor
几个核心钩子函数
Hooks 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。Hooks 是一种在函数式组件中使用有状态函数的方法。Hooks不支持在class中使用,比如在class中使用useState和useEffect都是不允许的。
- useEffect()函数:作用就是指定一个副效应函数,组件每渲染一次,该函数就自动执行一次。组件首次在网页 DOM 加载后,副效应函数也会执行。它的第二个参数,使用一个数组指定副效应函数的依赖项,只有依赖项发生变化,才会重新渲染。
- useState()函数:用于为函数组件引入状态。
- useContext():用于在组件之间共享状态。
- useReducer():React 本身不提供状态管理功能,通常需要使用外部库。这方面最常用的库是 Redux。Redux 的核心概念是,组件发出 action 与状态管理器通信。状态管理器收到 action 以后,使用 Reducer 函数算出新的状态,Reducer 函数的形式是(state, action) => newState。
Hook函数 | 说明 |
---|---|
useState | [<取值>, <设值>] = useState(<初始值>),useState可以保存状态,在状态改变的时候重新渲染视图,而普通变量会被重新赋值,无法保存状态 |
useEffect | 在组件加载和监听的对象值发生变化时调用 |
useContext | |
useLayoutEffect | |
useReducer | |
useCallback | 在依赖不变的情况下不返回新的函数地址而返回旧的函数地址。在往子组件传入了一个函数并且子组件被React.momo缓存了的时候使用 |
useMemo | |
useRef | 返回一个mutable ref对象,用.current获得其值。每次渲染 useRef 返回值都不变;ref.current 发生变化并不会造成 re-render; |
useTransition | |
useDeferredValue | |
useId |
useState用法
- useState的更新是异步的,数据不会立即刷新的。其背后是队列实现的。
- 仅顶层调用,不能在循环,条件,嵌套函数等中调用useState()。
useEffect典型用法
const [data, setData] = useState()
useEffect(() => {
// declare the async data fetching function
const fetchData = async () => {
// get the data from the api
const data = await fetch(`https://yourapi.com?param=${
param}`);
// convert the data to json
const json = await response.json();
// set state with the result
setData(json);
}
// call the function
fetchData()
// make sure to catch any error
.catch(console.error);;
}, [param])
再举一个例子:
import {
useState, useEffect } from 'react';
const HackerNewsStories = () => {
const [stories, setStories] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchStories = async () => {
try {
const data = await (await fetch('https://hn.algolia.com/api/v1/search_by_date?tags=front_page&hitsPerPage=20')).json();
setStories(
data.hits.sort((story, nextStory) => (story.points < nextStory.points ? 1 : -1))
);
setError(null);
} catch (err) {
setError(err.message);
setStories(null);
} finally {
setLoading(false);
}
};
fetchStories();
}, []);
return (
<div className="wrapper">
<h2>Latest HN Stories</h2>
{
loading && <div>HackerNews frontpage stories loading...</div>}
{
error && <div>{
`Problem fetching the HackeNews Stories - ${
error}`}</div>}
<div className="stories-wrapper">
{
stories &&
stories.map(({
objectID, url, title, author, points }) => (
title && url &&
<div className='stories-list' key={
objectID}>
<h3><a href={
url} target="_blank" rel="noreferrer">{
title}</a> - By <b>{
author}</b> ({
points} points)</h3>
</div>
))}
</div>
</div>
);
};
export default HackerNewsStories;
和原生模块交互
在使用 React 做开发时有两种写法:
类组件
函数组件 + Hook (用的这种)
React 组件之间共享数据的方案:
使用 React 自带的 Context + Reducer 功能 (用的这种)
优点:无须引入其他的包
缺点:只能向子组件及子孙组件中共享数据
使用 Redux 实际组件之间的共享
优点:数据全都放到 Redux 中管理,无论什么层级都直接使用
缺点:需要单独安装,数据状态由它统一管理,很多代码写法不太一样
RN 中的布局
View 组件默认是相对定位的(可以直接使用 left 、top 相对于原来的位置定位)
View 组件的绝对定位都是相对于父组件定位的(因为父组件都是相对定位,所以默认都是子绝父相)
没有浮动,只能 flex
父组件一定要设置高度或者 flex:1 ,否则高度为0页面空白
调用原生模块方法
要实现一个自己的ReactPackage和ReactModule,在ReactModule中暴露js方法。
调试
调试方面强烈推荐使用 React Native Debugger,一个基于 React Native 官方调试方式、包含 React Inspector / Redux DevTools 独立应用:
- 基于官方的 Remote Debugger 且提供了更为丰富的功能
- 包含 react-devtools-core 的 React Inspector
- 包含 Redux DevTools, 且与 redux-devtools-extension 保持 API 一致
命令行
- react-native
通过 react-native bundle 命令可对当前 RN 项目进行打包,以 iOS 为例
react-native bundle --platform ios --dev false \
--entry-file index.js \
--bundle-output __test__/example/index.ios.jsbundle \
--assets-dest __test__/example/
多个jsBundle异步加载
每个ReactApplication可以加载一个js bundle,如果将index.android.bundle拆成多个包,就得写多个ReactApplication。
每个ReactApplication对应一个ReactActivity,每个ReactActivity对应一个MainComponentName,即在js里注册的组件名称。
打包
不要用Android Studio打包,直接用命令:
./gradlew assembleRelease
需要在gradle配置文件中添加签名配置
其它工具
- Watchman, Watchman 在更改时观察文件和记录,然后触发相应的操作,并由 React Native 在内部使用
UI框架
其实RN提供的基本UI组件已经能实现大部分界面,但还有很多第三方UI库可供选择。
- react-native-elements
- 蚂蚁金服 Ant Design Mobile RN
- Lottie for React Native
- NativeBase
- React Native Material UI
- react-native-ui-kitten
- 雷数科技前端部门组件库/工具库
推荐阅读的开源项目
- F8: ReactNative作者们写的Conference App
- 在线订餐系统YumMeals
- 一款电影APP
- github客户端
- 流媒体音频app MindCast
- iReading阅读器
- https://github.com/stage88/react-weather
- https://github.com/wwayne/react-native-nba-app
- https://github.com/iSimar/HackerNews-React-Native
- https://github.com/ljunb/react-native-iShiWuPai
- https://github.com/BelinChung/react-native-hiapp
参考资源
- https://github.com/jondot/awesome-react-native
- https://github.com/reactnativecn/react-native-guide
- 下载 Genymotion免费版
- 多终端开发
- 矢量图标库
- js bridge的一个实现
- devio技术博客
- RNStudyNotes