React-native - 基础

主要讲解以下React的基础知识,方便同学们更好的学习项目的知识。

  • React Native 介绍
  • 文件目录结构
  • JSX
  • RN样式
  • 基本标签
  • 插值表达式
  • 调试
  • 事件
  • 生命周期
  • mobx

React Native 介绍

image-20200526120559183

文件目录结构

1
2
3
4
5
6
7
│  App.js           ---   项目的根组件
│ index.js --- 项目的入口文件
│ package.json --- 项目的描述文件
│ .eslintrc.js --- eslint的配置文件
│ .prettierrc.js --- 格式化配置文件
│ android --- 编译安卓相关
│ ios --- 编译ios相关

老师的vs code的插件

image-20200526173035552

JSX

React中写组件的代码格式 全称是 JavaScript xml

1
2
3
4
5
6
7
8
import React from 'react';
import { View, Text } from 'react-native';

const Index = () => <View>
<Text>JSX</Text>
</View>

export default Index;

RN样式

主要讲解和web开发的不同之处

  • flex布局
  • 样式继承
  • 单位
  • 屏幕宽度和高度
  • 变换

flex布局

  • 所有容器默认都是flexbox
  • 并且是纵向排列 也就是 flex-direction:column

样式继承与使用

样式继承

背景颜色、字体颜色、字体大小等没有继承

单位

  • 不能加 px 单位
  • 不能加 vw vh 等单位
  • 可以加百分比单位

屏幕宽度和高度

1
2
3
import {Dimensions } from "react-native";
const screenWidth = Math.round(Dimensions.get('window').width);
const screenHeight = Math.round(Dimensions.get('window').height);

变换

1
<Text style={{transform:[{translateY:300},{scale:2}]}}>变换</Text>

使用样式

第一种:直接使用
1
2
3
4
//对象方式
<View style={{width:20,height:30}}></View>
//数组方式
<View style={[{width:20,height:30},{height:20}]}></View>
第二种:公共使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//对象方式
<View style={styles.moduleStyle}></View>
//数组方式
<View style={[styles.moduleStyle,{height:20}]}></View>



//抽取的公共部分
const styles = StyleSheet.create({
moduleStyle: {
width: pTd(305),
height: pTd(201),
}
})
屏幕比例转换工具类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 创建文件 size.js
import { Dimensions } from 'react-native';

const deviceWidthDp = Dimensions.get('window').width;
const deviceHeightDp = Dimensions.get('window').height;
console.log('deviceWidthDp', deviceWidthDp, deviceHeightDp);

// 这里的uiWidthPx uiHeightPx 是你的设计稿的宽高尺寸
let uiWidthPx = 1280;
let uiHeightPx = 800;
console.log('转化比率------', deviceWidthDp / uiWidthPx);

// 如果应用是横屏的用pTd
export const pTd = uiElePx => {
return (uiElePx * deviceHeightDp) / uiHeightPx;
};

// 如果应用是竖屏的用pTx
export const pTx = uiElePx => {
return (uiElePx * deviceWidthDp) / uiWidthPx;
};
使用
1
2
3
4
//导入
import {pTd} from "../../utils/ratio";
//调用使用
pTx(20)

标签

  1. View

  2. Text

  3. TouchableOpacity

  4. Image

  5. ImageBackground

  6. TextInput

  7. 其他

    1. button
    2. FlatList
    3. ScrollView
    4. StatusBar
    5. TextInput

View

  • 相当于以前web中的div
  • 不支持设置字体大小,字体颜色等
  • 不能直接放文本内容
  • 不支持直接绑定点击事件 (一般使用 TouchableOpacity 来代替)

Text

文本标签

  • 文本标签 可以设置字体颜色、大小等
  • 支持绑定点击事件

TouchableOpacity

可以绑定点击事件的块级标签

  • 相当于块级的容器
  • 支持绑定点击事件 onPress
  • 可以设置点击时的透明度
1
<TouchableOpacity  activeOpacity={0.5}  onPress={this.handleOnPress} ></TouchableOpacity>

Image

图片标签

  • 渲染本地图片时

    1
    <Image source={require("../girl.png")}  />
  • 渲染网络图片时,必须加入宽度和高度

    1
    <Image source={{uri:"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1590514654506&di=38fa919d4c78fb776536b922bb94eec3&imgtype=0&src=http%3A%2F%2Fimages.ali213.net%2Fpicfile%2Fpic%2F2013%2F03%2F28%2F927_xgzwl%2520%25281%2529.jpg"}} style={{width:200,height:300}}  />
  • 在 Android 上支持 GIF 和 WebP 格式图片

    默认情况下 Android 是不支持 GIF 和 WebP 格式的。你需要在android/app/build.gradle文件中根据需要手动添加以下模块:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    dependencies {
    // 如果你需要支持Android4.0(API level 14)之前的版本
    implementation 'com.facebook.fresco:animated-base-support:1.3.0'

    // 如果你需要支持GIF动图
    implementation 'com.facebook.fresco:animated-gif:2.0.0'

    // 如果你需要支持WebP格式,包括WebP动图
    implementation 'com.facebook.fresco:animated-webp:2.1.0'
    implementation 'com.facebook.fresco:webpsupport:2.0.0'

    // 如果只需要支持WebP格式而不需要动图
    implementation 'com.facebook.fresco:webpsupport:2.0.0'
    }

    ImageBackground

一个可以使用图片当作背景的容器,相当于以前的 div+背景图片

1
2
3
<ImageBackground source={...} style={{width: '100%', height: '100%'}}>
<Text>Inside</Text>
</ImageBackground>

TextInput

输入框组件

  • 可以通过 onChangeText事件来获取输入框的值

语法

  1. 插值表达式

  2. 组件

  3. 状态 state

  4. 属性 props

  5. 调试

  6. 事件

  7. 生命周期

插值表达式

1
2
3
4
5
6
7
8
9
import React from 'react';
import { View, Text } from 'react-native';

const Index = () => <View>
<Text>{"开心"}</Text>
<Text>{123}</Text>
</View>

export default Index;

组件

  • 函数组件
    • 没有state (通过hooks可以有)
    • 没有生命周期(通过hooks可以有)
    • 适合简单的场景
  • 类组件
    • 适合复杂的场景
    • 有state
    • 有生命周期

函数组件

1
2
3
4
5
6
7
8
9
10
11
class Index extends Component {
render() {
return (
<View>
<Btn></Btn>
</View>
);
}
}
// 函数组件
const Btn = () => <Button title="点我" />

类组件

1
2
3
4
5
6
7
8
9
10
11
12
import React, { Component } from 'react';
import { View, Text } from 'react-native';
class Index extends Component {
render() {
return (
<View>
<Text>类组件</Text>
</View>
);
}
}
export default Index;

状态 state

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React, { Component } from 'react';
import { View, Text } from 'react-native';
class Index extends Component {
// 1 声明state
state = {
num: 100
}
render() {
return (
<View>
{/* 2 使用state */}
<Text onPress={this.handlePress} >{this.state.num}</Text>
</View>
);
}
// 3 修改state
handlePress = () => {
this.setState({ num: 1010 });
}
}
export default Index;

属性 props

父子传递数据的关键

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import React, { Component } from 'react';
import { View, Text } from 'react-native';
class Index extends Component {
render() {
return (
<View>
<BigText fontColor="red" >大博妞</BigText>
</View>
);
}
}

class BigText extends Component {
render() {
// 通过props来接收父组件传递的数据
return <Text style={{ color: this.props.fontColor }} >
{/* children 其实就是插槽 类似vue中的slot */}
{this.props.children}
</Text>
}
}


export default Index;

事件

绑定时间需要特别注意 this的指向问题,可以总结为如下的方式

  • 使用箭头函数
  • 通过bind重新绑定this
  • 匿名函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import React, { Component } from 'react';
import { View, Text } from 'react-native';
class Index extends Component {
state = { num: 100 }
// 丢失 state
handlePress1() {
console.log(this.state);
}
// 正常
handlePress2 = () => {
console.log(this.state);
}
// 正常
handlePress3() {
console.log(this.state);
}
// 正常
handlePress4() {
console.log(this.state);
}
// 正常
render() {
return (
<View>
{/* 导致事件函数中获取不到state */}
<Text onPress={this.handlePress1} >事件1</Text>
{/* 正常 */}
<Text onPress={this.handlePress2} >事件1</Text>
{/* 正常 */}
<Text onPress={this.handlePress3.bind(this)} >事件3</Text>
{/* 正常 */}
<Text onPress={() => this.handlePress4()} >事件4</Text>
</View>
);
}
}
export default Index;

总结

class事件传参
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//定义一个方法
_gotoSubClass(sectionID, rowID) {
console.log("sectionID="+sectionID + "rowID=" + rowID);
}
//方法传参1
<TouchableOpacity onPress={() => this._gotoSubClass(sectionID, rowID)}>
<Text>{rowData.title}</Text>
</TouchableOpacity>
//方法传参2
<TouchableOpacity onPress={this._gotoSubClass.bind(this,sectionID, rowID)}>
<Text>{rowData.title}</Text>
</TouchableOpacity>
其中bind中的参数1this代表的是上下文对象,后面跟的参数,对应私有方法中的参数顺序

hooks事件传参
1
2
3
4
5
6
7
//定义一个方法 接收参数
const syncInfo = (type) => {
console.log(type);
}

//onPresss事件时的传参
<Button buttonStyle={styles.syncButton} onPress={() => syncInfo('userLoginState')} title="同步" loading={userSyncState.userLoginState} type="clear"/>

生命周期

生命周期指的react组件的从创建到销毁的整个过程中会自动触发的函数

在线图示

image-20200528103625667

主要的生命周期

  • constructor
    • 组件被实例化的时候出发 一般用做对组件做初始工作,如设置state
  • render
    • 组件开始渲染时触发
    • 组件被更新时触发 - state和props发生改变时触发
  • componentDidMount
    • 组件挂载完毕,可以发送异步请求获取数据
  • componentWillUnmount
    • 组件被卸载时触发
    • 一般用在清除定时器或者取消订阅等

hook

1 Hook是什么

Hook可以在不使用class的情况下在函数组件中使用React的特性。

2 useState

useState 就是一个 Hook ,useState 用于在函数组件中添加内部 state。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 函数组件
import React, { useState } from 'react';

const demo = () => {
const [text, setText] = useState('啊');

return (
<div onClick={() => { setText('哈'); }}>
<p>{text}</p>
</div>
)
}
// 同等于class

// 同等于class

代码中申明了变量 text,默认值为 啊,可以通过 setText 来改变它的值。

3 useEffect

useEffect 可以在函数组件中达到生命周期函数的作用,可以把它看作componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React, { useState, useEffect } from 'react';

const demo = () => {
const [text, setText] = useState('啊');

useEffect(() => {
setText('咦');
})

return (
<div onClick={() => { setText('哈'); }}>
<p>{text}</p>
</div>
)
}

useEffect会在每次渲染后都执行,如果只想在某个或某些变量发生更改时执行,只需在useEffect中传递第二个参数 [变量名]。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React, { useState, useEffect } from 'react';

const demo = () => {
const [text, setText] = useState('啊');
const [text2, setText2] = useState('啊');
const [text3, setText3] = useState('啊');

useEffect(() => {
setText('咦');
},[text, text2])

return (
<div onClick={() => { setText('哈'); }}>
<p>{text}</p>
</div>
)
}

如上,这样useEffect只会在text和text2发生改变时执行,text3发生改变时不执行。

注意:如果第二个参数传递的为空数组,那么它只会在组件挂在和卸载的时候执行。

如果组件卸载时需要清除,那么直需要返回一个清除函数,效果同class组件中的componentWillUnmount。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React, { useState, useEffect } from 'react';

const demo = () => {
const [text, setText] = useState('啊');
const [text2, setText2] = useState('啊');
const [text3, setText3] = useState('啊');

useEffect(() => {
setText('咦');
return () => {
// 清除函数 用于组件卸载时做清除操作
}
},[text, text2])

return (
<div onClick={() => { setText('哈'); }}>
<p>{text}</p>
</div>
)
}

在一个函数组件中useEffect可以写多个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React, { useState, useEffect } from 'react';

const demo = () => {
const [text, setText] = useState('啊');
const [text2, setText2] = useState('啊');
const [text3, setText3] = useState('啊');

useEffect(() => {
setText('咦');
return () => {
// 清除函数 用于组件卸载时做清除操作
}
},[text, text2])

useEffect(() => {},[text])

return (
<div onClick={() => { setText('哈'); }}>
<p>{text}</p>
</div>
)
}

4 自定义Hook

通过自定义 Hook,可以将组件逻辑提取到可重用的函数中。
通过自定义 Hook,名称必须用use开头。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import React, { useState, useEffect } from 'react';

const useCommon = (text) => {
const [val, setVal] = useState(false);
useEffect(() => {
if (val) {
setVal(true)
}
})
return val
}

const demo = () => {
const text = useCommon('啊');

return (
<div>
<p>{text}</p>
</div>
)
}

const demo2 = () => {
const text = useCommon('哈');

return (
<div>
<p>{text}</p>
</div>
)
}

ref useRef

父与子间使用 ref

1
2
3
4
5
6
//定义一个ref 
const webView = useRef(null);


//接受组件的ref并赋值给定义的ref 赋值完后 webView可以调用子组件里的属性与方法
<WebView ref={(view) => (webView = view)} />

hooks与class间的的ref

1
2
3
4
5
6
7
8
9
10
//当子组件是class时 挂载完成后 把当前this暴露给父类
componentDidMount(){
this.props.onRef(this);
}

//当父组件是hooks时 接收子组件传来的ref
//定义useRef
const form = useRef();
//把子组件传来的属性赋值给当前的ref属性
<ModalDropdown onRef={f => form.current = f}> </ModalDropdown>

useState

useState是一个Hook函数,让你在函数组件中拥有state变量。它接收一个初始化的state,返回是一个数组,数组里有两个元素,第一个元素是当前状态值和另一个更新该值的方法。
本教程主要是针对于React中的useState做一个详细的描述,它等同于函数组件中的this.state/this.setState,我们将会围绕下面的问题逐一解析:

React中的类组件和函数组件

React.useState hook做了什么

在React中声明状态

React Hooks: Update State

在useState hook中使用对象作为状态变量

React Hooks中如何更新嵌套对象状态

多个状态变量还是一个状态对象

使用useState的规则

useReducer Hook的使用

如果您是刚开始学习使用useState,请查看官方文档或者该视频教程

React中的类组件和函数组件

React有两种类型的组件:类组件和函数组件。
类组件是继承React.Component的ES6类,它有自己的状态和生命周期函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Message extends React.Component {
constructor(props) {
super(props);
this.state = {
message: ""
};
}

componentDidMount() {
/*...*/
}

render() {
return <div>{this.state.message}</div>
}
}

函数组件是一个函数,它能接收任何组件的属性作为参数,并且可以返回有效的JSX。

1
2
3
4
5
function Message(props) {
return <div>{props.message} </div>
}
//或者使用箭头函数
const Message = (props) => <div>{props.message}</div>

正如所看到的,函数组件没有任何状态和生命周期方法。不过,在React16.8,我们可以使用Hooks。
React Hooks是方法,它可以给函数组件添加状态变量,并且可以模拟类组件的生命周期方法。他们倾向以use作为Hook名的开始。

React.useState hook做了什么

正如之前了解的,useState可以给函数组件添加状态,函数组件中的useState可以生成一系列与其组件相关联的状态。

类组件中的状态总是一个对象,不过Hooks中的状态可以是任意类型。每个state可以有单一的值,也可以是一个对象、数组、布尔值或者能想到的任意类型。

So,你会什么时候用useStateHook呢?它对组件自身的状态很有用,然而大项目可能会需要另外的状态管理方案。

React声明状态

useStateReact的命名输出出,因此你可以这么写:

1
React.useState

或者可以直接这么写:

1
import React, { useState } from "react";

然而不像在类组件里声明状态对象那样,useState允许声明多个状态变量:

1
2
3
4
5
6
7
8
9
10
11
12
import React from "react";

class Message extends React.Component {
constructor(props) {
super(props);
this.state = {
message: "",
list: "",
}
}
/*...*/
}

useStateHook一次只能声明一个状态变量,不过这个状态变量可以是任意类型的:

1
2
3
4
5
6
import React, { useState } from "react";

const Message = () => {
const messageState = useState("");
const listState = useState([]);
}

useState接收状态的初始值作为一个参数。
正如之前例子展示的,可以直接给函数传递,也可以使用函数来延迟初始化该变量(当初始化状态基于一次昂贵的计算,这种方式是很有用的):

1
2
3
4
const Message = () => {
const messageState = useState(() => expensiveComputation());
/*...*/
}

初始化的值仅仅会在第一次渲染时被赋值(如果他是一个函数,也是会在初次渲染时执行)。
在后续的更新中(由于组件本身的状态更改或者是说父组件导致的变化),useStateHook参数(初始值)将会被忽略,当前的值将会被使用。
理解它是非常重要的,举个例子,如果你想更新基于组件接收的新属性的状态:

1
2
3
const Message = (props) => {
const messageState = useState(props.message);
}

只单独使用useState不会工作的,因为它的参数仅仅在第一次生效,并不是每次属性更改时生效(可以结合useEffect使用,具体查看该回答
不过,useState不是像之前所说的仅仅返回一个变量。
它返回的是一个数组,第一个元素是状态变量,第二个元素是更新该变量值的方法。

1
2
3
4
5
const Message= () => {
const messageState = useState("");
const message = messageState[0]; // 是一个空字符串
const setMessage = messageState[1]; // 是一个方法
}

一般我们会选择数组解构的方式来简化上述代码:

1
2
3
const Message = () => {
const [message, setMessage] = useState("");
}

在函数组件中可以像其他变量一样使用状态变量:

1
2
3
4
const Message = () => {
const [message, setMessage] = useState("");
return <p>{message}</p>;
}

但是为什么useState会返回一个数组呢?
因为与对象相比、数组是非常灵活且容易使用。
如果这个方法返回的是一个包括一系列属性集的对象,那么就不能很容易自定义变量名,比如:

1
2
3
4
5
6
7
8
// 没有使用对象解构
const messageState = useState( '' );
const message = messageState.state;
const setMessage = messageState;

//使用对象解构
const { state: message, setState: setMessage } = useState( '' );
const { state: list, setState: setList } = useState( [] );

React Hooks: 更新状态

useState返回的第二个元素是一个方法,它用新值来更新状态变量。
举个🌰,使用输入框在每次改变时更新状态变量示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const Message = () => {
const [message, setMessage] = useState( '' );

return (
<div>
<input
type="text"
value={message}
placeholder="Enter a message"
onChange={e => setMessage(e.target.value)}
/>
<p>
<strong>{message}</strong>
</p>
</div>
);
};

然而,这个更新函数不会立即更新值。相反它会排队等待更新操作。在重新渲染组件后,useState的参数将被忽略,这个更新方法将会返回最新的值。
如果你需要用之前的值来更新状态,你一定得传递一个接收之前值的方法来返回新值示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const Message = () => {
const [message, setMessage] = useState("");

return (
<div>
<input
type="text"
value={message}
placeholder="Enter a message"
onChange={(e) => {
const val = e.target.value;
setMessage((prev) => prev + val);
}}
/>
<p>
<strong>{message}</strong>
</p>
</div>
);
};

useState hook中使用对象作为状态变量

当使用对象时,需要记住的是:

  1. 不可变的重要性
  2. useState返回的更新方法不是像类组件中的setState合并对象

关于第一点,如果你是用相同的值作为当前值来更新state(React使用的Object.is来做比较),React不会触发更新的。

当使用对象时,很容易出现下面的错误示例,输入框不能输入文本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const MessageOne = () => {
const [messageObj, setMessageObj] = useState({ message: "" });

return (
<div>
<input
type="text"
value={messageObj.message}
placeholder="Enter a message"
onChange={(e) => {
messageObj.message = e.target.value;
setMessageObj(messageObj);
}}
/>
<p>
<strong>{messageObj.message}</strong>
</p>
</div>
);
};

上面的例子没有创建一个新对象,而是改变已经存在的状态对象。对于React来说,它们是同一个对象。为了正常运行,我们创建一个新对象示例

1
2
3
4
onChange={(e) => {
const newMessageObj = { message: e.target.value };
setMessageObj(newMessageObj);
}}

这个让我们看到了你需要记住的第二件事情。
当你创建一个状态变量时,类组件中的this.setState 自动合并更新对象,而函数组件中useState的更新方法则是直接替换对象 。
继续上面的例子,如果我们给message对象添加一个id属性,将会发生什么呢示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const MessageThree = () => {
const [messageObj, setMessageObj] = useState({ message: "", id: 1 });

return (
<div>
<input
type="text"
value={messageObj.message}
placeholder="Enter a message"
onChange={(e) => {
const newMessageObj = { message: e.target.value };
setMessageObj(newMessageObj);
}}
/>
<p>
<strong>
{messageObj.id}: {messageObj.message}
</strong>
</p>
</div>
);
};

当只更新message属性时,React将替换原先的状态值{ message: '', id: 1 },当触发onChange属性时,状态值将仅仅包含message属性:

1
{message: "····"} // id属性丢失了

当然,通过替换的对象和扩展运算之前的对象结合作为参数也可以在函数组件中复制setState()的行为示例

1
2
3
4
5
6
onChange={(e) => {
const val = e.target.value;
setMessageObj((prevState) => {
return { ...prevState, message: val };
});
}}

...prevState会得到对象所有的属性,message: val会重新赋值给message属性。

当然使用Object.assign也会得到相同的结果(需要创建新对象)示例

1
2
3
4
//使用Object.assign
setMessageObj((prevState) => {
return Object.assign({}, prevState, { message: val });
});

不过扩展运算可以简化这个操作,而且也可以应用到数组上。
一般来讲,当在数组上使用时,扩展运算移除了括号,你可以用旧数组中的值创建另一个数组:

1
2
3
4
5
6
[
...['a', 'b', 'c'],
'd'
]
// Is equivalent to
[ 'a', 'b', 'c','d']

再来个🌰,如何用数组来使用useState示例
需要注意的是,处理多维数组时需要谨慎的使用扩展运算,因为可能最终的结果不是你所期待的。
所以这个时候,我们就需要考虑使用对象作为状态。

React Hooks中如何更新嵌套对象状态

JS中,多维数组是数组里嵌套数组:

1
2
3
4
[
['value1','value2'],
['value3','value4']
]

你可以在用它们来把你所有的状态变量集中在一个地方,然而,为了这个目的,最好使用内嵌对象:

1
2
3
4
5
6
7
8
9
10
{
'row1' : {
'key1' : 'value1',
'key2' : 'value2'
},
'row2' : {
'key3' : 'value3',
'key4' : 'value4'
}
}

当使用内嵌对象和多维数组时,有一个问题是Object.assign和扩展运算是创建了一个浅拷贝并非是深拷贝。

当拷贝数组时,扩展运算仅仅做了一层的拷贝,因此,对于多维数组来说,使用它是不合适的,就像下面例子中所示(使用Object.assign()和扩展元算结果都是true):

1
2
3
4
let a = [[1], [2], [3]];
let b = [...a];
b.shift().shift(); // 1
//此时数组a的结果是[[], [2], [3]]

StackOverflow关于上面的例子提供了一个比较好的解释,不过目前重要的是,当使用内嵌对象时,我们不能用扩展运算来更新状态对象。

再举个🌰

1
2
3
4
5
6
7
const [messageObj, setMessageObj] = useState({
author: "",
message: {
id: 1,
text: "",
}
});

来,先看看更新text字段的错误方式示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//错误, 文本更改后,messageObj的值是{author: "", message: {id: 1, text: ""}, text: "*"}
setMessageObj((prevState) => ({
...prevState,
text: val,
}));

//错误,文本更改后messageObj值是{id: "*", text: "*"}
setMessageObj((prevState) => ({
...prevState.message,
text: val
}));

//错误,文本更改后,messageObj的值是{author: "", message: {text: "*"}},缺少id属性
setMessageObj((prevState) => ({
...prevState,
message: {
text: val,
}
}));

为了能正确的更新text属性,我们需要拷贝一个原始对象,这个新对象包括整个原始对象的所有属性:

1
2
3
4
5
6
7
setMessageObj((prevState) => ({
...prevState, //赋值第一层的key值
message: { //创建包含更新key值的对象
...prevState.message, //复制包含key值的对象
text: val, //给需要更新的字段重新赋值
}
}));

以同样的方式,我们也可以更新author字段:

1
2
3
4
setMessageObj((prevState) => ({
author: "Joe",
message: { ...prevState.message },
}));

如果message对象变化了,则用以下的方式:

1
2
3
4
5
6
7
setMessageObj((prevState) => ({
author: "Joe",
message: {
...prevState.message,
text: val,
},
}));

声明多个状态变量还是一个状态对象

当你的应用中使用多个字段或值作为状态变量时,你可以选择组织多个变量:

1
2
3
const [id, setId] = useState(-1);
const [message, setMessage] = useState('');
const [author, setAuthor] = useState('');

或者使用一个对象状态变量:

1
2
3
4
5
const [messageObj, setMessage] = useState({ 
id: 1,
message: '',
author: ''
});

不过,需要谨慎的使用复杂数据结构(内嵌对象)的状态对象,考虑下这个🌰 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const [messageObj, setMessage] = useState({
input: {
author: {
id: -1,
author: {
fName:'',
lName: ''
}
},
message: {
id: -1,
text: '',
date: new Date(),
}
}
});

如果你需要更新嵌套在对象深处的指定字段时,你必须复制所有其他对象和包含该指定字段的的对象的键值对一起复制:

1
2
3
4
5
6
7
8
9
setMessage(prevState => ({
input: {
...prevState.input,
message: {
...prevState.input.message,
text: '***',
}
}
}));

在某些情况下,拷贝深度内嵌对象是比较昂贵的,因为React可能会依赖那些没有改变过的字段值重新渲染你应用的部分内容。
对于这个原因,首先要做的是尝试扁平化你的对象。需要关注的是,React官方推荐根据哪些值倾向于一起变化,将状态分割成多个状态变量
如果这个不可能的话,推荐使用第三方库来帮助你使用不可变对象,例如immutable.js或者 immer

useState使用规则

useState和所有的Hooks一样,都遵循同样的规则:

  • 在顶层调用Hooks
  • 在React函数中调用Hooks

第二个规则很容易理解,不要在类组件中使用useState

img

或者在常规JS方法中(不能在一个方法组件中被调用的):

img

如果项目中有ESLint的话, 则可以看到对应错误提示;如果没有,则可以从该文档中看到出现的错误提示

第一个规则指的是:即使在函数组件内部,不能在循环、条件或者内嵌方法中调用useState,因为React依赖于useState函数被调用的顺序来获取特定变量的正确值。
在这方面,常见的错误是,在if语句使用useState(它们不是每次都被执行的):

1
2
3
4
5
6
if (condition) { // 有时它会执行,导致useState的调用顺序发生变化
const [message, setMessage] = useState( '' );
setMessage( aMessage );
}
const [list, setList] = useState( [] );
setList( [1, 2, 3] );

函数组件中会多次调用useState或者其他的Hooks。每个Hook是存储在链表中的,而且有一个变量来追踪当前执行的Hook。
useState被执行时,当前Hook的状态被读取(第一次渲染期间是被初始化),之后,变量被改变为指向下一个Hook。
这也就是为什么总是始终保持Hook相同的调用顺序是很重要的。否则的话,状态值就会属于另一个状态变量。

总体来说,这儿有一个例子来一步步说明它是如何工作的:

  1. React初始化Hook链表,并且用一个变量追踪当前Hook
  2. React首次调用你的组件
  3. React发现了useState的调用,创建了一个新的Hook对象(带有初始状态),将当前Hook变量指向该Hook对象,将该对象添加到Hooks链表中,然后返回一个带有初始值和更新状态方法的数组
  4. React发现另一个useState的调用,重复上面的步骤,存储新的hook对象,改变当前Hook变量。
  5. 组件状态发生变化
  6. React给要处理的队列发送新的状态更新操作(执行useState返回的方法)
  7. React决定组件是否需要重新渲染
  8. React重置当前Hook变量,并且调用组件
  9. React发现了一个useState的调用,但此时,在Hooks链表第一位置已经有一个Hook,它仅仅改变了当前Hook变量,返回一个带状态值和更新状态方法的数组
  10. React发现另一个useState的调用,因为第二个位置有一个Hook了,它再次仅仅改变了当前Hook变量,返回一个带状态值和更新状态方法的数组
    这是一个简单的useState工作流程,具体可以看ReactFiberHooks源码

useReducerHook的使用

对于很多高级使用情况,我们可以使用useReducerHook来代替useState。当处理复杂的状态逻辑时,它是很有用的。比如包含多个子值或者状态依赖之前的值。
Look,看下如何使用useReducerHook,示例:

1
2
const [state, dispatch] = useReducer(reducer, initialArgument, init);
//useReducer返回一个带有当前状态值和dispatch方法的数组。如果你使用过Redux,这个就得心应手了。

useState,你调用更新状态的方法,然而useReducer中,你调用dispatch方法,然后传递给它一个action,eg: 至少是带有一个type属性的对象。

1
dispatch({type"increase"})

一般来讲,一个action对象也会有其他的属性,eg: {action: "increase", payload: "10"}
然而传递一个action对象并不是绝对的,具体参考Redux

总结

uesState是一个Hook函数,它让你可以在组件中拥有状态变量,你可以给这个方法传递初始值,并且返回一个当前状态值的变量(不一定是初始值)和另一个更新该值的方法。

这儿有一些需要记住的关键的点:

  • 更新方法不会立即更新值
  • 如果你需要用到之前的值更新状态,你必须将之前的值传递给该方法,则它会返回一个更新后的值,eg: setMessage(previousVal => previousVal + currentVal)
  • 如果你使用同样的值作为当前状态,则React不会触发重新渲染。(React是用Object.is来做比较的)
  • useState不像类组件中的this.setState合并对象,它是直接替换对象。
  • useState和所有的hooks遵循相同的规则,特别是,注意这些函数的调用顺序。(可以借助 ESLint plugin,它会帮助我们强制实施这些规则)

React-Native-属性、控件、样式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
/**
* Sample React Native App
* https://github.com/facebook/react-native
* @flow
*/

//PropTypes 使用属性是必须导入
import React,{ Component , PropTypes } from 'react';

//使用控件时必须导入,StyleSheet创建属性是必须导入
import {AppRegistry, Text , Image , View , StyleSheet} from 'react-native';

//export default 必须写,导出,外部才可以访问
export default class test extends Component {

render(){//所哟的类必须有自己的reder方法,用于输入组件,return里面必须返回一个组件,只能是一个组件,多个用view包裹


//在此可以声明变量
let picURL = {//参数必须为uri,其他的图片显示不出来
uri:'https://upload.wikimedia.org/wikipedia/commons/d/de/Bananavarieties.jpg'
};

return (
/*
* 加载图片
*
* 加载网络图片,必须要指定宽和高,不然显示不出来,网络图片不确定图片的size
*
* 加载本地图片,不需要指定宽和高,当然也可以自定义,因为本地图片确定了图片的size
* */

//使用自定义控件时,只给它的属性赋值即可

<View style={ViewStyle.myStyleSelect}>


<Image source={picURL} style={{width: 100, height:100}}></Image>


<Image source={require('./image/cat1.jpg')} style={ViewStyle.myStyleSelect1}></Image>

<Greetings abc="jick"/>
<Greetings abc="luch"/>
<Greetings abc="math"/>


<Person name={'于小宝'} age={23}/>
<Person name={'李大头'} age={34}/>
</View>
);
}

}

//自定义控件,显示格式统一'hello+字符串',所以可以自定义一个view自带hello,填入后面的字符串即可
class Greetings extends Component {
render(){

return(

<Text>Hello {this.props.abc} !</Text>//hello+属性字符串

);

}
}

//自定义控件,声明两个属性name和age,返回文本
class Person extends Component {

//声明属性的方法
static propTypes={

name :PropTypes.string,//指定属性的类型
age :PropTypes.number

};



render(){


return(
<Text style={{fontWeight:'bold',lineHeight:30}}>姓名:{this.props.name};年龄:{this.props.age}</Text>
);


}


}

//自定义样式,里面可以指定多个样式,使用时 ViewStyle.myStyle 即可
var ViewStyle =StyleSheet.create({

myStyle:{
alignItems:'center',
},
myStyleSelect:{
alignItems:'center',
backgroundColor:'red',
borderWidth:2,
borderColor:'#00ff00',
borderStyle:'dotted',

},
myStyleSelect1:{
bottom:20,
left:20,

}

});

/*
*ReactNative中能使用的css样式
Valid style props: [

"alignItems",居中对齐弹性盒的各项 <div> 元素
例:alignItems:'center',
-> stretch(项目被拉伸以适应容器)
-> center(项目位于容器的中心)
-> flex-start(项目位于容器的开头)
-> flex-end(项目位于容器的结尾)
-> baseline(项目位于容器的基线上)
-> initial(设置该属性为它的默认值)
-> inherit(从父元素继承该属性)

"alignSelf",居中对齐弹性对象元素内的某个项
例:alignSelf:'center',
-> auto(默认值。元素继承了它的父容器的 align-items 属性。如果没有父容器则为 "stretch")
-> stretch(项目被拉伸以适应容器)
-> center(项目位于容器的中心)
-> flex-start(项目位于容器的开头)
-> flex-end(项目位于容器的结尾)
-> baseline(项目位于容器的基线上)
-> initial(设置该属性为它的默认值)
-> inherit(从父元素继承该属性)

"backfaceVisibility",当元素不面向屏幕时是否可见
例:backfaceVisibility:'visible'
-> visible (背面是可见的)
-> hidden (背面是不可见的)

"backgroundColor",背景色
例:backgroundColor:'red'
例:backgroundColor:'#cccccc'
-> color (指定背景颜色。在CSS颜色值近可能的寻找一个颜色值的完整列表)
-> transparent (指定背景颜色应该是透明的。这是默认)
-> inherit (指定背景颜色,应该从父元素继承)

"borderBottomColor",底部边框颜色
例:borderBottomColor:'red'
例:borderBottomColor:'#cccccc'
-> color (指定背景颜色。在CSS 颜色值 查找颜色值的完整列表)
-> transparent (指定边框的颜色应该是透明的。这是默认)
- >inherit (指定边框的颜色,应该从父元素继承)


"borderBottomLeftRadius",左下角添加圆角边框
例:borderBottomLeftRadius:10

"borderBottomRightRadius",右下角添加圆角边框
例:borderBottomRightRadius:10

"borderBottomWidth",底部边框宽度
例:borderBottomWidth:8

"borderColor",四个边框颜色
例:borderColor:'#00ff00'

"borderLeftColor",左边框颜色
例:borderLeftColor:'yellow'

"borderLeftWidth",左边框宽度
例:borderLeftWidth:10

"borderRadius",四角圆角弧度
例:borderRadius:15

"borderRightColor",右边框颜色
例:例:borderRightColor:'yellow'

"borderRightWidth",右边框宽度
例:borderRightWidth:10

"borderStyle",四个边框的样式
例:borderStyle:'dotted'
-> none 定义无边框。
-> hidden 与 "none" 相同。不过应用于表时除外,对于表,hidden 用于解决边框冲突。
-> dotted 定义点状边框。在大多数浏览器中呈现为实线。
-> dashed 定义虚线。在大多数浏览器中呈现为实线。
-> solid 定义实线。
-> double 定义双线。双线的宽度等于 border-width 的值。
-> groove 定义 3D 凹槽边框。其效果取决于 border-color 的值。
-> ridge 定义 3D 垄状边框。其效果取决于 border-color 的值。
-> inset 定义 3D inset 边框。其效果取决于 border-color 的值。
-> outset 定义 3D outset 边框。其效果取决于 border-color 的值。
-> inherit 规定应该从父元素继承边框样式。

"borderTopColor",上边框颜色
例:borderTopColor:'red'

"borderTopLeftRadius",左上角圆角弧度
例:borderTopLeftRadius:3

"borderTopRightRadius",右上角圆角弧度
例:borderTopRightRadius:4

"borderTopWidth",顶部边框宽度
例:borderTopWidth:13

"borderWidth",四个边框的宽度
例:borderWidth:2

"bottom",图像的底部边缘
例:bottom:20

"color",颜色
例:color:'red'

"elevation",Z轴,可产生立体效果
例:elevation:1

"flex",让所有弹性盒模型对象的子元素都有相同的长度,忽略它们内部的内容
例:flex:1

"flexDirection",设置 <div> 元素内弹性盒元素的方向为相反的顺序
例:flexDirection:'column'
-> row ((默认值。灵活的项目将水平显示,正如一个行一样)
-> row-reverse (与 row 相同,但是以相反的顺序)
-> column (灵活的项目将垂直显示,正如一个列一样)
-> column-reverse (与 column 相同,但是以相反的顺序)
-> initial (设置该属性为它的默认值。请参阅 initial)
-> inherit (从父元素继承该属性。请参阅 inherit)

"flexWrap",让弹性盒元素在必要的时候拆行
例:flexWrap:'wrap'
-> nowrap (默认值。规定灵活的项目不拆行或不拆列)
-> wrap (规定灵活的项目在必要的时候拆行或拆列)
-> wrap-reverse (规定灵活的项目在必要的时候拆行或拆列,但是以相反的顺序)
-> initial (设置该属性为它的默认值。请参阅 initial)
-> inherit (从父元素继承该属性。请参阅 inherit)

"fontFamily",字体
例:fontFamily:'courier'

"fontSize",字体大小
例:fontSize:20

"fontStyle",
例:fontStyle:'italic'
-> normal 默认值。浏览器显示一个标准的字体样式。
-> italic 浏览器会显示一个斜体的字体样式。
-> oblique 浏览器会显示一个倾斜的字体样式

"fontWeight",文本的粗细
例:fontWeight:'bold'
-> normal (默认值。定义标准的字符)
-> bold (定义粗体字符)
-> bolder (定义更粗的字符)
-> lighter (定义更细的字符)
-> 100 (定义由粗到细的字符。400 等同于 normal,而 700 等同于 bold)
-> 200
-> 300
-> 400
-> 500
-> 600
-> 700
-> 800
-> 900

"height",设置元素的高度
例:height:200

"justifyContent",在弹性盒对象的元素中的各项周围留有空白
例:justifyContent:'space-between'
-> flex-start (默认值。项目位于容器的开头)
-> flex-end (项目位于容器的结尾)
-> center (项目位于容器的中心)
-> space-between (项目位于各行之间留有空白的容器内)
-> space-around (项目位于各行之前、之间、之后都留有空白的容器内)

"left",把图像的左边缘设置在其包含元素左边缘向右5像素的位置
例:left:20

"letterSpacing",字母间距
例:letterSpacing:20

"lineHeight",行高
例:lineHeight:30

"margin",元素的所有四个边距
例:

"marginBottom",下边距
例:marginBottom:50

"marginHorizontal",水平间距,即左边距和右边距
例:marginHorizontal:10

"marginLeft",左边距
例:marginLeft:50

"marginRight",右边距
例:marginRight:50

"marginTop",上边距
例:marginTop:50

"marginVertical",垂直间距,即上边距和下边距
例:marginVertical:10

"opacity",透明度级别
例:opacity:0.

WebView的使用[混合html开发]

安装WebView

1
2
3
4
5
yarn add react-native-webview

或者

npm install --save react-native-webview

WebView 组件介绍

使用 WebView 组件可通过 url 来加载显示一个网页,也可以传入一段 html 代码来显示。下面对其主要属性和方法进行介绍。

  1. 主要属性

source:在 WebView 中载入一段静态的 html 代码或是一个 url(还可以附带一些 header 选项);
automaticallyAdjustContentInsets:设置是否自动调整内容。格式:bool;
contentInset:设置内容所占的尺寸大小。格式:{top:number,left:number,bottom:number,right:number};
injectJavaScript:当网页加载之前注入一段 js 代码。其值是字符串形式。
startInLoadingState:是否开启页面加载的状态,其值为 true 或者 false。
bounces(仅iOS):回弹特性。默认为 true。如果设置为 false,则内容拉到底部或者头部都不回弹。
scalesPageToFit(仅iOS):用于设置网页是否缩放自适应到整个屏幕视图,以及用户是否可以改变缩放页面。
scrollEnabled(仅iOS):用于设置是否开启页面滚动。
domStorageEnabled(仅Android):用于控制是否开启 DOM Storage(存储)。
javaScriptEnabled(仅Android):是否开启 JavaScript,在 iOS 中的 WebView 是默认开启的。

  1. 主要方法

onNavigationStateChange:当导航状态发生变化的时候调用。
onLoadStart:当网页开始加载的时候调用。
onError:当网页加载失败的时候调用。
onLoad:当网页加载结束的时候调用。
onLoadEnd:当网页加载结束调用,不管是成功还是失败。
renderLoading:WebView组件正在渲染页面时触发的函数,只有 startInLoadingState 为 true 时该函数才起作用。
renderError:监听渲染页面出错回调函数。
onShouldStartLoadWithRequest(仅iOS):该方法允许拦截 WebView 加载的 URL 地址,进行自定义处理。该方法通过返回 true 或者 false 来决定是否继续加载该拦截到请求。
onMessage:在webView内部网页中,调用window.postMessage可以触发此属性对应的函数,通过event.nativeEvent.data获取接收到的数据,实现网页和RN之间的数据传递。
injectJavaScript:函数接受一个字符串,该字符串将传递给WebView,并立即执行为JavaScript。

通过 url 地址加载网页

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import React, {Component} from 'react';
import {
AppRegistry,
StyleSheet,
Dimensions,
Text,
View,
WebView
} from 'react-native';

//获取设备的宽度和高度
var {
height: deviceHeight,
width: deviceWidth
} = Dimensions.get('window');

//默认应用的容器组件
class App extends Component {
//渲染
render() {
return (
<View style={styles.container}>
<WebView bounces={false}
scalesPageToFit={true}
source={{uri:"https://shq5785.blog.csdn.net/",method: 'GET'}}
style={{width:deviceWidth, height:deviceHeight}}>
</WebView>
</View>
);
}
}

//样式定义
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop:20
}
});

AppRegistry.registerComponent('HelloWorld', () => App);

通过 html加载 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import React, {Component} from 'react';
import {
AppRegistry,
StyleSheet,
Dimensions,
Text,
View,
WebView
} from 'react-native';

//获取设备的宽度和高度
var {
height: deviceHeight,
width: deviceWidth
} = Dimensions.get('window');

//默认应用的容器组件
class App extends Component {
//渲染
render() {
return (
<View style={styles.container}>
<WebView bounces={false}
scalesPageToFit={true}
source={{html:"<h1 style='color:#ff0000'>欢迎访问 https://shq5785.blog.csdn.net/</h1>"}}
style={{width:deviceWidth, height:deviceHeight}}>
</WebView>
</View>
);
}
}

//样式定义
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop:20
}
});

AppRegistry.registerComponent('HelloWorld', () => App);

RN -> HTML5 通信

WebView加载html时,可实现htmlrn之间的通信。rnhtml发送数据可以通过postMessage函数实现。如下:

RN

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<WebView
ref={(view) => (this.webView = view)}
useWebKit={false}
useWebKit={false}
onLoad={() => {
let data = {
name: userInfo.usrName
};
this.webView.postMessage(JSON.stringify(data));
}}
onError={(event) => {
console.log(`==webViewError:${JSON.stringify(event.nativeEvent)}`);
}}
onMessage={(event) => {
this._onH5Message(event);
}}
automaticallyAdjustContentInsets={false}
contentInset={{ top: 0, left: 0, bottom: -1, right: 0 }}
onScroll={(event) => this._onScroll(event)}
style={styles.webview}
source={this.html ? { html: this.html } : { uri: this.url }}
bounces={false}
showsHorizontalScrollIndicator={false}
showsVerticalScrollIndicator={false}
/>

html

1
2
3
4
5
6
7
8
9
10
// 在html中注册事件接收rn发过来的数据并显示在html中
document.addEventListener('message', function listener(RnData) {
messagesReceivedFromReactNative += 1;
document.getElementsByTagName('p')[0].innerHTML =
'从React Native接收的消息: ' + messagesReceivedFromReactNative;
document.getElementsByTagName('p')[1].innerHTML = RnData.data;
// 获取接收后的数据后,及时清除监听器
document.removeEventListener('message', listener)
});

html中定义一个按钮,并添加事件向rn发送数据:

1
2
3
4
5
6
7
//window.postMessage向rn发送数据
document.getElementsByTagName('button')[0].addEventListener('click', function() {
window.ReactNativeWebView.postMessage(JSON.stringify({
message: "html发来的消息"
}));
});

html中调用了window.postMessage函数后,WebViewonMessage函数将会被回调,用来处理htmlrn发送的数据,可以通过e.nativeEvent.data获取发送过来的数据。

1
2
3
4
5
6
7
8
9
// 接收HTML发出的数据
_onH5Message = (e) => {
this.setState({
messagesReceivedFromWebView: this.state.messagesReceivedFromWebView + 1,
message: e.nativeEvent.data,
})
Alert.alert(e.nativeEvent.data)
}

JavaScript 脚本注入方式

注:这种方式适用于 react-native-webviewRN本身没有试过)。

android开发中,需要使用 javaScriptEnabled属性来支持JavaScriptios默认是支持的,没有此属性。在WebView中提供了函数injectJavaScript(String),它有一个字符串参数,可以向webview中注入脚本,如下:

1
2
3
4
5
6
7
8
//脚本注入
injectJS = () => {
const script = 'document.write("Injected JS ")'; // eslint-disable-line quotes
if (this.webview) {
this.webview.injectJavaScript(script);
}
}

实现思路:通过injectJavaScript 注入JS ,在H5页面加载之后立即执行。相当于webview端主动调用H5的方法。

注意事项:injectJavaScript注入的必须是js。注入内容可以是方法实现,也可以是方法名字。

注意:

  • 注入函数名的时候,实际上注入的仍然是函数实现。
  • 当注入js方法名需要传递参数的时候,可提前将函数名作为字符串,函数参数作为变量,生成一个字符串,然后将此字符串注入。

RN端

首先Webview绑定 ref='webView'

H5调用一个名为 receiveMessage 的函数,并传入一个字符串, 参数true不可少

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<WebView
ref={(view) => (this.webView = view)}
useWebKit={false}
useWebKit={false}
onLoadEnd={() => {
this.endLoad();
}}
..
/>

//加载结束调用,不管是成功还是失败。
endLoad(){
// 在H5调用一个名为 `receiveMessage` 的函数,并传入一个字符串, 参数true不可少
this.refs.webView.injectJavaScript(`receiveMessage("RN向H5发送消息");true;`)
}

H5端(Vue实现)

1
2
3
4
5
6
mounted(){ 
//在window上挂载一个receiveMessage方法,RN会调用
window.receiveMessage = (msg) => {
alert( msg)
}
},

WebView引入到react-native案例[html与react-native通信]

react native端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
import React, {useEffect, useRef, useState} from 'react';
import {
View,
StyleSheet, Alert, Text,
} from 'react-native'
import {inject, observer} from "mobx-react";
import {pTd} from "../../utils/ratio";
import WebView from "react-native-webview";


const Index = ({baseStore, navigation}) => {

let webView = useRef(null);

const [userInfo, setUserInfo] = useState({
userInfo: {
usrName: '张三'
}
});

//触发的事件
const goBack = () => {
navigation.navigate("Home");
}


// 接收HTML发出的数据
const _onH5Message = (e) => {
console.log("-=-=-=",e)
let data = JSON.parse(e.nativeEvent.data)
if (data.backtrack){
goBack()
}

}

return (
<View style={{flex: 1}}>
<WebView
ref={(view) => (webView = view)}
useWebKit={false}
mixedContentMode={'always'}
originWhitelist={["*"]}
startInLoadingState={true}
onLoad={() => {
let data = {
name: userInfo.usrName
};
webView.postMessage(JSON.stringify(data));
}}
onError={(event) => {
console.log(`==webViewError:${JSON.stringify(event.nativeEvent)}`);
}}
onMessage={(event) => {
_onH5Message(event);
}}
automaticallyAdjustContentInsets={false}
//此处用的是地址的方式引入
source={{uri: 'http://localhost:8001/index.html'}}
bounces={false}
showsHorizontalScrollIndicator={false}
showsVerticalScrollIndicator={false}
/>
</View>
)

}


const styles = StyleSheet.create({

titleContent: {
display: "flex",
flexDirection: "row",
justifyContent: "center",
alignItems: "center",
marginTop: "1.5%"

}
})

export default inject('baseStore')(observer(Index));

html端

1
2
3
4
5
6
function backtrackClick() {
window.ReactNativeWebView.postMessage(JSON.stringify({
backtrack: true
}));
};