中文网站:https://reactnative.cn/docs/getting-started
安装环境
在Mac平台上开发React Native需要安装以下环境和工具:
Note.js
React Native Command Line Tools
XCode/AndroidStudio
1、安装Node.js
React Native开发需要用到Node.js环境。我们做React Native开发会经常性的和Node.js进行打交道,在Mac上安装 Node.js可以通过Homebrew,打开终端运行如下命名:
brew install node
安装完 Node 后建议设置 npm 镜像以加速后面的过程(如果能翻墙可以省略这步):
npm config set registry https://registry.npm.taobao.org --global
npm config set disturl https://npm.taobao.org/dist --global
2、安装React Native命令行工具
Note.js安装成功之后,接下来我们就可以通过npm install来安装React Native命令行工具了。
打开终端,输入并执行下面命令即可完成安装:
npm install -g react-native-cli
3、安装iOS开发工具XCode/android开发工具android studio
4、安装一些命令行工具
Watchman则是由 Facebook 提供的监视文件系统变更的工具。安装此工具可以提高开发时的性能(packager 可以快速捕捉文件的变化从而实现实时刷新)。
brew install watchman
Yarn是 Facebook 提供的替代 npm 的工具,可以加速 node 模块的下载。React Native 的命令行工具用于执行创建、初始化、更新项目、运行打包服务(packager)等任务。
npm install -g yarn
安装完 yarn 后同理也要设置镜像源:
yarn config set registry https://registry.npm.taobao.org --global
yarn config set disturl https://npm.taobao.org/dist --global
安装 CocoaPods(iOS端依赖),安装见https://blog.csdn.net/zkdemon/article/details/90241630
5、创建自己第一个react-native项目
使用 React Native 命令行工具来创建一个名为"TestReactNative"的新项目:
react-native init TestReactNative
自动调用CocoaPods安装依赖包如果失败的话,手动到ios目录下pod install一下(Podfile文件中第一行添加源地址 source 'https://github.com/CocoaPods/Specs.git')
ios和android目录提前先用xcode和android studio编译运行一下(android端需要提前在AVD Manager中创建模拟器,创建要求见https://reactnative.cn/docs/getting-started中的介绍)
android端第一次运行的时候app里面可能会报js错误
修改项目目录/android/app/build.gradle里
加入
entryFile: "index.js",
bundleAssetName: "index.android.bundle",
bundleInDebug: true,
bundleInRelease: true
这几句重新运行就没问题了。
后边就直接可以在项目目录中运行以下命令就可以编译运行:
cd TestReactNative
// 运行IOS
yarn ios
// 运行android(提前需要把模拟器开开,在运行命令)
yarn android
编译成功可以看到一个简单的demo:
组件component
1、组件创建方式
常用的组件创建方式有两种,第一种是单纯一个JavaScript函数,第二种是创建一个JS类
import React, {Component} from 'react';
import { Text, View } from 'react-native';
// 方式一:
const MyComponent = (props) => (
<View>
<Text>MyComponent</Text>
</View>
);
// 方式二:
class MyClass extends Component {
render() {
return (
<View>
<Text>MyClass</Text>
</View>
);
}
}
export { MyClass };
export default MyComponent;
以上两种方式创建的组件用法完全一样,区别在于方式一无法复写组件的声明周期,无法维护state,关于声明周期与state我们将在以后章节里面讲解。
2、组件和模块的导出和引入
1)export 命令
一个独立的文件就是一个模块。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。下面是一个 JS 文件,里面使用export命令输出变量。如上方的自定义组件中的
export { MyClass };
export default MyComponent;
-
export defalt命令在每个文件中只能存在一个,顾名思义是导出组件的默认输出。
-
接下来介绍export的几种写法
// 方式一:分别导出三个变量 export const firstName = 'Michael'; export const lastName = 'Jackson'; export const year = 1958; // 方式二:用大括号统一导出(和方式一效果一样) const firstName = 'Michael'; const lastName = 'Jackson'; const year = 1958; export { firstName, lastName, year }; // 除了导出变量,还可以导出方法和类 export function logExport() { console.log('export'); } class MyClass extends Component { render() { return ( <View> <Text>MyClass</Text> </View> ); } } export default MyClass; // 使用as关键字给输出重新命名 export { firName as firstName };
2)import 命令
使用export命令定义了模块的对外接口以后,其他 JS 文件就可以通过import命令加载这个模块。
// 非export default输出的,需要使用大括号
// 和export相同也可以使用as 取别名
import { firstName, MyComponent ,year as yearName } from './MyComponent';
// 使用export default时,对应的import语句不需要使用大括号
import MyClass from './MyComponent';
// 本质上,export default就是输出一个叫做default的变量或方法,然后系统允许你为它取任意名字。所以,下面的写法是有效的。
import OtherName from './MyComponent';
通过 export 和import 我们就可以在app.js中使用自己创建的组件了
...
import OtherName from './MyComponent';
...
export default class App extends Component<Props> {
render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>Welcome to React Native!</Text>
<Text style={styles.instructions}>To get started, edit App.js</Text>
<Text style={styles.instructions}>{instructions}</Text>
<OtherName />
</View>
);
}
}
组件生命周期
生命周期
如图,可以把组件生命周期大致分为三个阶段:
-
第一阶段:是组件第一次绘制阶段,如图中的上面虚线框内,在这里完成了组件的加载和初始化,其中getDefaultProps,getInitialState 在新版的Component中ES6语法继承时,直接复写方法会报异常,RN API要求我们props,state的初始化在Component的constructor函数中完成;
-
第二阶段:是组件在运行和交互阶段,如图中左下角虚线框,这个阶段组件可以处理用户交互,或者接收事件更新界面;
-
第三阶段:是组件卸载消亡的阶段,如图中右下角的虚线框中,这里做一些组件的清理工作。
进入一个RN界面时,Component的声明周期函数大致流程如下:
-
1.constructor构造函数,从上一个界面传过来的数据props在作为constructor的参数,在constructor中做一些初始化的操作,如props,state等初始化;函数原型:void constructor(props)
-
2.componentWillMount,第一次绘制组件(render)前触发该生命周期函数;函数原型:void componentWillMount()
-
3.render绘制组件到界面上;函数原型:void render()
-
4.componentDidMount,第一次挥之组件(render)后触发该生命周期函数,触发该函数说明RN组件绘制已经完成了,虚拟DOM已经构建完成;从这个函数开始我们就可以和JS其他框架开始交互了,例如定时器,网络请求等操作。函数原型:void componentDidMount()
-
5.componentWillReceiveProps,当组件收到新的属性时,触发该函数。函数原型:void componentWillReceiveProps( object nextProps )
-
6.shouldComponentUpdate,当组件属性(props)或者状态(state)发生变化时,都会触发该函数。函数原型:boolean shouldComponentUpdate( object nextProps, object nextState ); 若函数返回true,则继续触发componentWillUpdate,render,componentDidUpdate等方法;若函数返回false,则不做任何处理;默认情况下,这个函数永远返回true用来保证数据变化的时候 UI 能够同步更新。在大型项目中,你可以自己重载这个函数,通过检查变化前后属性和状态,来决定 UI 是否需要更新,能有效提高应用性能。
-
7.componentWillUpdate,如果组件状态或者属性改变,并且上面的shouldComponentUpdate(object nextProps, object nextState)返回为true,则触发该方法,函数原型:void componentWillUpdate(object nextProps, object nextState),函数中参数实际上与shouldComponentUpdate中的参数一样,是同一个数据源;需要特别注意的是,在这个函数里面,你就不能使用this.setState来修改状态,否则会循环调用该函数导致堆栈溢出。这个函数调用之后,就会把nextProps和nextState分别设置到this.props和this.state中。紧接着这个函数,就会调用render()来更新界面了。
-
8.componentDidUpdate,更新render结束后,则调用该方法,函数原型:void componentDidUpdate(object prevProps, object prevState);因为到这里已经完成了属性和状态的更新了,此函数的输入参数变成了prevProps和prevState。
-
9.componentWillUnmount,当组件要被从界面上移除的时候,就会调用componentWillUnmount(),函数原型:void componentWillUnmount();在这个函数中,可以做一些组件相关的清理工作,例如取消计时器、网络请求等。
插件
(1)React Navigation 4.x
官网文档https://reactnavigation.org/docs/getting-started/
4.x 版本从 react-navigation
中移除了各类导航器,同时还依赖了一些其他的包需要手动安装
npm install react-navigation react-native-reanimated react-native-gesture-handler react-native-screens react-native-safe-area-context
npm install react-navigation-stack @react-native-community/masked-view react-navigation-tabs
reactnative 0.60 版本之后,安装完成之后会自动 link。
Android 端需要手动进行一些修改,编辑 android/app/build.gradle
,在 dependencies
中添加如下内容:
implementation 'androidx.appcompat:appcompat:1.1.0-rc01'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-alpha02'
编辑 Android 中的 MainActivity.java
,添加如下内容:
package com.reactnavigation.example;
import com.facebook.react.ReactActivity;
import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.ReactRootView;
import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;
public class MainActivity extends ReactActivity {
@Override
protected String getMainComponentName() {
return "Example";
}
@Override
protected ReactActivityDelegate createReactActivityDelegate() {
return new ReactActivityDelegate(this, getMainComponentName()) {
@Override
protected ReactRootView createRootView() {
return new RNGestureHandlerEnabledRootView(MainActivity.this);
}
};
}
}
iOS端需要进入项目的ios目录执行:
pod install
使用时界面下边如果经常报js警告:
Calling
getNode() on the ref of an Animated component is no longer necessary. You can now directly use the ref instead. This method will be removed in a future release.
找到项目node_modules/react-native-safe-area-view/index.js,修改192行
把this.view.getNode().measureInWindow((winX, winY, winWidth, winHeight)改为this.view.measureInWindow((winX, winY, winWidth, winHeight),警告就消失了。
创建tabbar及导航
import React from 'react';
import {View,Text,Image,Button} from 'react-native';
import {createAppContainer} from 'react-navigation';
import {createBottomTabNavigator} from 'react-navigation-tabs';
import {createStackNavigator} from 'react-navigation-stack';
import App1 from './App1';
import App2 from './App2';
import Introd from './Introd';
class nav1 extends React.Component {
render() {
return (
<View>
<Text>this is nav1</Text>
<Button
title={'点击前往Introd'}
onPress={() => {
this.props.navigation.push('欢迎页')
}}
/>
</View>
)
}
}
const chiStack = createStackNavigator({
"次页": {
screen: App2,
},
"欢迎页": {
screen: Introd,
},
},
{
initialRouteName: "次页",
});
const qStack = createStackNavigator({
"导航": {
screen: nav1,
},
"欢迎页": {
screen: Introd,
},
},
{
initialRouteName: "导航",
});
const TabNavigator = createBottomTabNavigator(
{
"App1":{
screen: App1,
navigationOptions: {
title: "首页",
tabBarIcon:({ focused, horizontal, tintColor })=>{
return <Image source={require('./img/1.png')} style={{width: 10, height: 10}}/>
}
}
},
"App2":{
screen: chiStack,
navigationOptions: {
title: "次页",
tabBarIcon:({ focused, horizontal, tintColor })=>{
return <Image source={require('./img/2.png')} style={{width: 10, height: 10}}/>
}
}
},
"导航":{
screen: qStack
}
},
{
tabBarOptions: {
showIcon: false,
activeTintColor: 'black',
inactiveTintColor: 'gray',
labelStyle: {
fontSize: 12,
},
style: {
backgroundColor: 'white',
}
}
}
);
export default createAppContainer(TabNavigator);
React Navigation 5.x
npm install @react-navigation/native
npm install react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context @react-native-community/masked-view
npm install @react-navigation/stack
npm install @react-navigation/bottom-tabs
iOS端需要进入项目的ios目录执行:
pod install
创建tabbar及导航
import 'react-native-gesture-handler';
import React,{Component} from 'react';
import {View,Text,Image,Button,SafeAreaView} from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import App1 from './App1';
import App2 from './App2';
import Introd from './Introd';
import {
Cat,
Bananas,
} from './utils';
class nav1 extends React.Component {
render() {
return (
<SafeAreaView>
<View>
<Text>this is nav1</Text>
<Button
title={'点击前往Introd'}
onPress={() => {
this.props.navigation.navigate('欢迎页')
}}
/>
</View>
<Bananas name="App"/>
</SafeAreaView>
)
}
}
const navStack = createStackNavigator();
export class navStackScreen extends Component {
render() {
return (
<navStack.Navigator>
<navStack.Screen name="导航页s" component={nav1} />
<navStack.Screen name="欢迎页" component={Introd} />
</navStack.Navigator>
);
}
}
const ciStack = createStackNavigator();
export class ciStackScreen extends Component {
render() {
return (
<ciStack.Navigator>
<ciStack.Screen name="次页" component={App2} />
<ciStack.Screen name="欢迎页1" component={Introd} />
</ciStack.Navigator>
);
}
}
const Tab = createBottomTabNavigator();
export default function App() {
return (
<NavigationContainer>
<Tab.Navigator
screenOptions={({ route }) => ({
tabBarIcon: ({ focused, color, size }) => {
if (route.name === '次页') {
return <Image source={require('./img/1.png')} style={{width: 25, height: 25}}/>;
} else {
return <Image source={require('./img/2.png')} style={{width: 25, height: 25}}/>;
}
},
})}
tabBarOptions={{
activeTintColor: 'tomato',
inactiveTintColor: 'gray',
}}
>
<Tab.Screen name="首页" component={App1} />
<Tab.Screen name="次页" component={ciStackScreen} />
<Tab.Screen name="导航页" component={navStackScreen} />
</Tab.Navigator>
</NavigationContainer>
);
}
在没有导航器的情况下进行导航:https://reactnavigation.org/docs/navigating-without-navigation-prop/