React Native开发速记

引子

软件开发,移动优先;移动开发,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库可供选择。

推荐阅读的开源项目

参考资源

猜你喜欢

转载自blog.csdn.net/jgku/article/details/130711345