先看demo

写在最前面
从开始学RN到现在大概有2个星期天左右了,这里先记录一下,也算个小阶段总结。就目前感觉,RN的优势和劣势都很明显;
- 优势- RN是混合开发一份代码多端使用
- 代码与前端相似,Web转RN比较轻松
 
- 劣势- RN组件由多个第三方维护,更新不可控,会有停更不兼容的风险
- 会由于RN版本,组件版本,Xcode版本的不同而随机组合成各种坑(这点很令人烦躁,大部分时间都浪费在这)
- 多端兼容适配成本大,而且会随着项目规模而增大难度,到一定程度开发成本会比原生的高,如Airbnb宣布放弃使用RN,回归原生技术
 
正文
一 环境安装
官网说的很详细,按照官网的步骤基本没问题,就不多赘述
官网地址: https://reactnative.cn/docs/getting-started.html
二 熟悉RN
创建Q项目,并用iOS模拟器运行起来
 react-native init q
 cd q
 react-native run-ios项目内容如下:
- android:Android工程文件 
- ios: iOS工程文件 
- node_modules: React-native组件依赖存放的文件夹 
- package.json: 依赖配置json, 类似于cocoPods中的“Podfile” 
- index.js: 项目入口 ...  
先看index.js, import 是引入文件,AppRegistry.registerComponent(appName, () => App);整个的意思就是将工程目录的App.js注册成组件并引入,所以一开始显示的即App.js里面的内容
import {AppRegistry} from 'react-native';
import App from './App';
import {name as appName} from './app.json';
AppRegistry.registerComponent(appName, () => App);
App.js文件里面大致可以分成三部分
- 引入组件
- 搭建UI
- 样式
有过前端开发经验的朋友对View,Text,ScrollView这些并不陌生,在React-native中,所有组件都要单独引入,所有组件及作用可看官方文档
import React from 'react';
import {
  SafeAreaView,
  StyleSheet,
  ScrollView,
  View,
  Text,
  StatusBar,
} from 'react-native';
import {
  Header,
  LearnMoreLinks,
  Colors,
  DebugInstructions,
  ReloadInstructions,
} from 'react-native/Libraries/NewAppScreen';
这里部分内容是画UI,基本上和html没差多少,都是用各种组件的堆砌。学过web或者小程序之类的看起来会很简单,没学过的话,建议选去学学最基本的html + css
const App: () => React$Node = () => {
  return (
      <StatusBar barStyle="dark-content" />
      <SafeAreaView>
        <ScrollView
        .....
        .....
         </SafeAreaView>
    </>
  );
};
export default App;这里是各种样式,大部分都是沿用css的,看到这里大概感觉到React-native其实就是前段代码换个壳,对于有前段知识的人学起来应该会很轻松,没有相关知识的话建议还是先去学学基础的再来搞React-native
const styles = StyleSheet.create({
  scrollView: {
    backgroundColor: Colors.lighter,
  },
   .....
   .....
});三 尝试Demo
有这些了解后,可以试着做一个简单的列表页面

1.导入组件,需要的组件大概有这些,重点是FlatList列表组件
import React, { Component } from "react";
import { Image, FlatList, StyleSheet, Text, View } from "react-native";2.导出默认类App扩展组件,包括住下面的其他代码
export default class App extends Component {
}3.创建个长度是8的data数组,后面可给FlatList赋值用
constructor(props) {
    super(props);
    this.state = {
      data: [{}, {}, {}, {}, {}, {}, {}, {}],
    };
}4.RN的render()函数实际上跟iOS的ViewDidLoad()函数相似,返回的就是具体的内容,内容很固定,只能是一个组件,这里我返回的是FlatList,字段说明如下
- data:需要给予一个数组,数组长度与列表item对应
- style:列表样式
- renderItem:Item渲染,这里是直接调用renderMovie渲染
- keyExtractor:设置每个item唯一的key,类似于索引
render() {
    return (
      <FlatList
        data={this.state.data}
        style={styles.list}
        renderItem={this.renderMovie.bind(this)}  
        keyExtractor={item => item.id}
      />
    );
 }5.通过renderMovie函数返回item的内容,这里可以任意发挥
renderMovie({ item }) {
    return (
        <View style={styles.container}>
          <Image
            source={{ uri: 'https://gss3.bdstatic.com/7Po3dSag_xI4khGkpoWK1HF6hhy/baike/w%3D268%3Bg%3D0/sign=e4d6ea2325dda3cc0be4bf2639d25e3c/b64543a98226cffcb1f7cc0eb2014a90f703eaa9.jpg' }}
            style={styles.thumbnail}
          />
          <View style={styles.rightContainer}>
            <View style={styles.titleWithout}>
              <Text style={styles.title}>罗小黑战记</Text>
              <Text style={styles.tip}>中国巨屏</Text>
            </View>
            <Text style={styles.score}>猫眼评分<Text style={styles.grade}> 9.8 </Text></Text>
            <Text style={styles.starring}>主演:罗小黑,罗小白</Text>
            <Text style={styles.cinema}>今天129加音乐反映124场</Text>
          </View>
          <Text style={styles.buy}>购买</Text>
        </View>
    );
  } 6.最后是样式,其实这些随意发挥即可
这样简单的一个页面就完成了,完整代码如下,可以直接copy替代原有内容运行即可看到效果
import React, { Component } from "react";
import { Image, FlatList, StyleSheet, Text, View, TouchableOpacity } from "react-native";
export default class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      data: [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {},],
    };
  }
  render() {
    return (
      <FlatList
        data={this.state.data}
        style={styles.list}
        renderItem={this.renderMovie.bind(this)}  
        keyExtractor={item => item.id}
      />
    );
  }
  renderMovie({ item }) {
    return (
        <View style={styles.container}>
          <Image
            source={{ uri: 'https://gss3.bdstatic.com/7Po3dSag_xI4khGkpoWK1HF6hhy/baike/w%3D268%3Bg%3D0/sign=e4d6ea2325dda3cc0be4bf2639d25e3c/b64543a98226cffcb1f7cc0eb2014a90f703eaa9.jpg' }}
            style={styles.thumbnail}
          />
          <View style={styles.rightContainer}>
            <View style={styles.titleWithout}>
              <Text style={styles.title}>罗小黑战记</Text>
              <Text style={styles.tip}>中国巨屏</Text>
            </View>
            <Text style={styles.score}>猫眼评分<Text style={styles.grade}> 9.8 </Text></Text>
            <Text style={styles.starring}>主演:罗小黑,罗小白</Text>
            <Text style={styles.cinema}>今天129加音乐反映124场</Text>
          </View>
          <Text style={styles.buy}>购买</Text>
        </View>
    );
  }
}
var styles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: "row",
    justifyContent: "center",
    alignItems: "center",
    backgroundColor: "#fff"
  },
  header: {
    height: 20,
    marginTop: 44,
  },
  rightContainer: {
    flex: 1,
    paddingLeft: 18,
  },
  titleWithout: {
    flexDirection: "row",
    fontWeight: "bold",
    alignItems: "center",
  },
  title: {
    fontSize: 14,
    marginTop: 4,
    lineHeight: 0,
  },
  tip: {
    backgroundColor: "#999",
    fontSize: 8,
    textAlign: "center",
    color: "#fff",
    height: 14,
    width: 40,
    lineHeight: 14,
    borderRadius: 2,
    marginLeft: 4,
    marginTop: 4,
  },
  score: {
    paddingTop: 8,
    fontSize: 12,
    color: "#666",
  },
  starring: {
    paddingTop: 4,
    fontSize: 12,
    color: "#666",
  },
  cinema: {
    paddingTop: 4,
    fontSize: 12,
    color: "#666",
  },
  buy: {
    fontSize: 12,
    // color:'#333',
    width: 46,
    height: 24,
    lineHeight: 24,
    textAlign: "center",
    backgroundColor: "#D44145",
    color: "#fff",
    borderRadius: 12,
    marginRight: 20,
  },
  grade: {
    color: "#DF8D7A",
  },
  year: {
    textAlign: "center"
  },
  thumbnail: {
    width: 68,
    height: 94,
    marginLeft: 20,
    marginTop: 10,
    marginBottom: 10,
  },
  list: {
    paddingTop:40,
    backgroundColor: "#F5FCFF"
  },
  headerOutline: {
    backgroundColor: "#fff",
    marginTop: 44,
  },
  headerInside: {
    backgroundColor: "#f5f5f5",
    flexDirection: "row",
    justifyContent: 'space-between',
    marginLeft: 20,
    marginRight: 20,
    marginBottom: 6,
    paddingTop: 10,
    paddingBottom: 4,
    paddingLeft: 10,
    paddingRight: 10,
  },
  trendIcon: {
    width: 10,
    height: 14,
    marginLeft: 10,
    marginTop: -1,
  },
  trendText: {
    height: 22,
    color: '#333',
    fontWeight: "bold",
  },
  trendR: {
    color: '#333',
    fontSize: 10,
    fontWeight: "bold",
    height: 22,
  },
  trendRText: {
  },
  trendMoney: {
    color: '#D24349',
  },
});二 Navigation与Tabbar
如图,最终目的是创建一个带Navigation,Tabbar的demo,可分为三个步骤
- 安装组件
- 创建tabbar上的两个跟根页面和一个详情页面
- 修改index.js入口

1.先安装react-navigation组件
注:这里有个天坑,react-navigation4与3差距很大,现在网上的教程基本使用的都是react-navigation 3,这里我也是兜兜转转搞了许久才意识到的,大家都是初学者,建议安装版本3
yarn add react-navigation@3.5.1
yarn add react-native-gesture-handler
这里可能会报这个错
Error: Requiring unknown module "447". If you are sure the module exists, try restarting Metro. You may also want to run `yarn` or `npm install`.
这个错原因很多,可以尝试
npm install
react-native run-ios或者
cd ios
pod install
cd ..
react-native run-ios2.创建detailsScreen.js,settingScreen.js,navigation.js文件
detailsScreen.js内容
import React from 'react';
import {
    View,
    Text,
    Button,
    Image,
} from 'react-native';
export default class detailsScreen extends React.Component {
    render() {
        return (
            <View style={{flex:1, alignItems:'center',justifyContent: 'center'}}><Text>详情页</Text></View>
        );
    }
}settingScreen.js内容
import React from 'react';
import {
    View,
    Text,
    Button,
    Image,
} from 'react-native';
export default class settingScreen extends React.Component {
    render() {
        return (
            <View style={{flex:1, alignItems:'center',justifyContent: 'center'}}><Text>设置页</Text></View>
        );
    }
}navigation.js内容需要分步讲解一下,首先引入所有需要的组件与页面
import React from 'react';
import { Text } from 'react-native'
import HomeScreen from "./App";        
import DetailsScreen from "./detailScreen";
import SettingScreen from "./settingScreen";
import {
    createStackNavigator,
    createAppContainer,
    createBottomTabNavigator
} from 'react-navigation';
这里是声明HomeStack,SettingsStack两个组件;
createStackNavigator 提供APP屏幕之间切换的能力,它是以栈的形式还管理屏幕之间的切换,新切换到的屏幕会放在栈的顶部。
const HomeStack = createStackNavigator({
    Home: { screen: HomeScreen, }
})
const SettingsStack = createStackNavigator({
    Settings: { screen: SettingScreen },
})
这里声明TabNavigator
createBottomTabNavigator(RouteConfigs, BottomTabNavigatorConfig)相当于iOS里面的TabBarController,屏幕下方的标签栏
- RouteConfigs(必选):路由配置对象是从路由名称到路由配置的映射,告诉导航器该路由呈现什么。
- BottomTabNavigatorConfig(可选):配置导航器的路由(如:默认首屏,navigationOptions,paths等)样式(如,转场模式mode、头部模式等)。
const TabNavigator = createBottomTabNavigator(
    {
      Home: { screen: HomeStack },
      Settings: { screen: SettingsStack }
    },
    {
      navigationOptions: () => ({
        header: null
      }),
      defaultNavigationOptions: ({ navigation }) => ({
        tabBarLabel: ({ tintColor}) => {
          const { routeName } = navigation.state
          switch (routeName) {
            case 'Home':
              return <Text style={{ color: tintColor, fontSize: 12 }}>{'首页'}</Text>
            case 'Settings':
              return <Text style={{ color: tintColor, fontSize: 12 }}>{'设置'}</Text>
          }
        },
        tabBarIcon: ({ focused, tintColor }) => {
            let urld 
            const { routeName } = navigation.state
            switch (routeName) {
                case 'Home':
                    return <Image source={{ uri: 'https://static.easyicon.net/preview/119/1191814.gif' }} style={[{height: 20, width: 20}]}/>    
                case 'Settings':
                    return <Image source={{ uri: 'https://static.easyicon.net/preview/121/1215319.gif' }} style={[{height: 20, width: 20}]}/>    
            }
        }
      }),
      tabBarOptions: {
        inactiveTintColor: 'gray',
      }
    }
)
最后设置路由并返回
const AppStack = createStackNavigator({
    Tabs: TabNavigator,
    Details: { screen: DetailsScreen },
  }, {
    defaultNavigationOptions: () => ({
    })
  })
export default createAppContainer(AppStack)
完整代码如下
import React from 'react';
import { Text,Image} from 'react-native'
import HomeScreen from "./App";     
import DetailsScreen from "./detailScreen";
import SettingScreen from "./settingScreen";
import {
    createStackNavigator,
    createAppContainer,
    createBottomTabNavigator
} from 'react-navigation';
const HomeStack = createStackNavigator({
    Home: { screen: HomeScreen, }
})
const SettingsStack = createStackNavigator({
    Settings: { screen: SettingScreen },
})
const TabNavigator = createBottomTabNavigator(
    {
      Home: { screen: HomeStack },
      Settings: { screen: SettingsStack }
    },
    {
      navigationOptions: () => ({
        header: null
      }),
      defaultNavigationOptions: ({ navigation }) => ({
        tabBarLabel: ({ tintColor}) => {
          const { routeName } = navigation.state
          switch (routeName) {
            case 'Home':
              return <Text style={{ color: tintColor, fontSize: 12 }}>{'首页'}</Text>
            case 'Settings':
              return <Text style={{ color: tintColor, fontSize: 12 }}>{'设置'}</Text>
          }
        },
        tabBarIcon: ({ focused, tintColor }) => {
            let urld 
            const { routeName } = navigation.state
            switch (routeName) {
                case 'Home':
                    return <Image source={{ uri: 'https://static.easyicon.net/preview/119/1191814.gif' }} style={[{height: 20, width: 20}]}/>    
                case 'Settings':
                    return <Image source={{ uri: 'https://static.easyicon.net/preview/121/1215319.gif' }} style={[{height: 20, width: 20}]}/>    
            }
        }
      }),
      tabBarOptions: {
        inactiveTintColor: 'gray',
      }
    }
)
const AppStack = createStackNavigator({
    Tabs: TabNavigator,
    Details:DetailsScreen,
  }, {
    defaultNavigationOptions: () => ({
    })
  })
export default createAppContainer(AppStack)
3.修改index.js入口
这里仅仅只是把入口改为navigation.js
import {AppRegistry} from 'react-native';
import Nav from './navigation.js';
import {name as appName} from './app.json';
AppRegistry.registerComponent(appName, () => Nav);
保存基本就能看到App的架子大概形成了
 
接下来要设置一下点击事件,让demo可以跳转
先回到App.js页面 设置首页导航栏标题
static navigationOptions = ({ navigation }) => {
    const { params = {} } = navigation.state
    const onPressRightButtonFunc = params.openPublisher || function () { }
    return {
      title: '首页',
    }
}引入TouchableOpacity设置点击事件
import {  TouchableOpacity } from "react-native";
 ...
 ...
 renderMovie({ item }) {
    const navigate = this.props.navigation;
    return (
      <TouchableOpacity activeOpacity={0.5} onPress={() => navigate.navigate('Details')} > //'Details'是之前在navigation.js声明好的了
      ... //这里是之前item的UI代码
      </TouchableOpacity>
到这里基本已经完成了这个demo,其他的都是一些重复的UI工作也不赘述了,这是稍微优化过的代码和详情页,看不懂的可以根据根据这源码来。
这里我的源码是将基本组件都下好,下载运行即可,因为比较大先上传到百度云。
链接: https://pan.baidu.com/s/1854tyx1R_Bjz4A57xvxn1g 提取码: kgmb
网上的其他demo对新人都很不友好,需要安装各个组件再运行起来,各种报错容易劝退新人
后记
初衷是想让新手快速的入门制作一个demo,后面发现还是需要一点web经验的,内容有点多,说得没那么细致的地方请见谅。后续会一直持续更新这个demo;



