从一个React Native Demo开始(内附Demo源码)

3,303 阅读9分钟

先看demo

写在最前面

从开始学RN到现在大概有2个星期天左右了,这里先记录一下,也算个小阶段总结。就目前感觉,RN的优势和劣势都很明显;

  • 优势
    • RN是混合开发一份代码多端使用
    • 代码与前端相似,Web转RN比较轻松
  • 劣势
    • RN组件由多个第三方维护,更新不可控,会有停更不兼容的风险
    • 会由于RN版本,组件版本,Xcode版本的不同而随机组合成各种坑(这点很令人烦躁,大部分时间都浪费在这)
    • 多端兼容适配成本大,而且会随着项目规模而增大难度,到一定程度开发成本会比原生的高,如Airbnb宣布放弃使用RN,回归原生技术

正文

一 环境安装

官网说的很详细,按照官网的步骤基本没问题,就不多赘述

官网地址: reactnative.cn/docs/gettin…

二 熟悉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
  • 样式

有过前端开发经验的朋友对ViewTextScrollView这些并不陌生,在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,可分为三个步骤

  1. 安装组件
  2. 创建tabbar上的两个跟根页面和一个详情页面
  3. 修改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-ios

2.创建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工作也不赘述了,这是稍微优化过的代码和详情页,看不懂的可以根据根据这源码来。

这里我的源码是将基本组件都下好,下载运行即可,因为比较大先上传到百度云。

链接: pan.baidu.com/s/1854tyx1R… 提取码: kgmb

网上的其他demo对新人都很不友好,需要安装各个组件再运行起来,各种报错容易劝退新人

后记

初衷是想让新手快速的入门制作一个demo,后面发现还是需要一点web经验的,内容有点多,说得没那么细致的地方请见谅。后续会一直持续更新这个demo;

如果觉得对这篇文章对您有一点帮助的话,欢迎关注,戳这里 → 芦苇科技