主要讲解以下React的基础知识,方便同学们更好的学习项目的知识。
React Native 介绍 文件目录结构 JSX RN样式 基本标签 插值表达式 调试 事件 生命周期 mobx
文件目录结构 1 2 3 4 5 6 7 │ App .js --- 项目的根组件 │ index.js --- 项目的入口文件 │ package.json --- 项目的描述文件 │ .eslintrc .js --- eslint的配置文件 │ .prettierrc .js --- 格式化配置文件 │ android --- 编译安卓相关 │ ios --- 编译ios相关
老师的vs code的插件
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布局 所有容器默认都是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 import { Dimensions } from 'react-native' ;const deviceWidthDp = Dimensions .get ('window' ).width ;const deviceHeightDp = Dimensions .get ('window' ).height ;console .log ('deviceWidthDp' , deviceWidthDp, deviceHeightDp);let uiWidthPx = 1280 ;let uiHeightPx = 800 ;console .log ('转化比率------' , deviceWidthDp / uiWidthPx);export const pTd = uiElePx => { return (uiElePx * deviceHeightDp) / uiHeightPx; }; export const pTx = uiElePx => { return (uiElePx * deviceWidthDp) / uiWidthPx; };
使用 1 2 3 4 import {pTd} from "../../utils/ratio" ;pTx (20 )
View
Text
TouchableOpacity
Image
ImageBackground
TextInput
其他
button FlatList ScrollView StatusBar TextInput View 相当于以前web
中的div 不支持设置字体大小,字体颜色等 不能直接放文本内容 不支持直接绑定点击事件 (一般使用 TouchableOpacity
来代替) Text 文本标签
文本标签 可以设置字体颜色、大小等 支持绑定点击事件 TouchableOpacity 可以绑定点击事件的块级标签
相当于块级的容器 支持绑定点击事件 onPress
可以设置点击时的透明度 1 <TouchableOpacity activeOpacity={0.5 } onPress={this .handleOnPress } ></TouchableOpacity >
Image 图片标签
ImageBackground 一个可以使用图片当作背景的容器,相当于以前的 div+背景图片
1 2 3 <ImageBackground source={...} style={{width : '100%' , height : '100%' }}> <Text > Inside</Text > </ImageBackground >
TextInput 输入框组件
可以通过 onChangeText
事件来获取输入框的值 语法 插值表达式
组件
状态 state
属性 props
调试
事件
生命周期
插值表达式 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可以有) 适合简单的场景 类组件 函数组件 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 { state = { num : 100 } render ( ) { return ( <View > {/* 2 使用state */} <Text onPress ={this.handlePress} > {this.state.num}</Text > </View > ); } 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 ( ) { return <Text style ={{ color: this.props.fontColor }} > {/* children 其实就是插槽 类似vue中的slot */} {this.props.children} </Text > } } export default Index ;
事件 绑定时间需要特别注意 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 } 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); } <TouchableOpacity onPress={() => this ._gotoSubClass (sectionID, rowID)}> <Text > {rowData.title}</Text > </TouchableOpacity > <TouchableOpacity onPress ={this._gotoSubClass.bind(this,sectionID, rowID )}> <Text > {rowData.title}</Text > </TouchableOpacity > 其中bind中的参数1 :this 代表的是上下文对象,后面跟的参数,对应私有方法中的参数顺序
hooks事件传参 1 2 3 4 5 6 7 const syncInfo = (type ) => { console .log (type); } <Button buttonStyle={styles.syncButton } onPress={() => syncInfo ('userLoginState' )} title="同步" loading={userSyncState.userLoginState } type="clear" />
生命周期指的react组件
的从创建到销毁的整个过程中会自动触发的函数
在线图示
主要的生命周期 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
代码中申明了变量 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,你会什么时候用useState
Hook呢?它对组件自身的状态很有用,然而大项目可能会需要另外的状态管理方案。
React声明状态 useState
是React
的命名输出出,因此你可以这么写:
或者可以直接这么写:
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 : "" , } } }
useState
Hook一次只能声明一个状态变量,不过这个状态变量可以是任意类型的:
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 ()); }
初始化的值仅仅会在第一次渲染时被赋值(如果他是一个函数,也是会在初次渲染时执行)。 在后续的更新中(由于组件本身的状态更改或者是说父组件导致的变化),useState
Hook参数(初始值)将会被忽略,当前的值将会被使用。 理解它是非常重要的,举个例子,如果你想更新基于组件接收的新属性的状态:
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中使用对象作为状态变量 当使用对象时,需要记住的是:
不可变的重要性 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
属性:
当然,通过替换的对象和扩展运算之前的对象结合作为参数也可以在函数组件中复制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 setMessageObj ((prevState ) => { return Object .assign ({}, prevState, { message : val }); });
不过扩展运算可以简化这个操作,而且也可以应用到数组上。 一般来讲,当在数组上使用时,扩展运算移除了括号,你可以用旧数组中的值创建另一个数组:
1 2 3 4 5 6 [ ...[ 'a', 'b', 'c'] , 'd' ] [ '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();
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 setMessageObj ((prevState ) => ({ ...prevState, text : val, })); setMessageObj ((prevState ) => ({ ...prevState.message , text : val })); setMessageObj ((prevState ) => ({ ...prevState, message : { text : val, } }));
为了能正确的更新text
属性,我们需要拷贝一个原始对象,这个新对象包括整个原始对象的所有属性:
1 2 3 4 5 6 7 setMessageObj ((prevState ) => ({ ...prevState, message : { ...prevState.message , 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
:
或者在常规JS方法中(不能在一个方法组件中被调用的):
如果项目中有ESLint的话, 则可以看到对应错误提示;如果没有,则可以从该文档中看到出现的错误提示 。
第一个规则指的是:即使在函数组件内部,不能在循环、条件或者内嵌方法中调用useState
,因为React依赖于useState
函数被调用的顺序来获取特定变量的正确值。 在这方面,常见的错误是,在if语句使用useState
(它们不是每次都被执行的):
1 2 3 4 5 6 if (condition) { const [message, setMessage] = useState ( '' ); setMessage ( aMessage ); } const [list , setList] = useState ( [] );setList ( [1 , 2 , 3 ] );
函数组件中会多次调用useState
或者其他的Hooks。每个Hook是存储在链表中的,而且有一个变量来追踪当前执行的Hook。 当useState
被执行时,当前Hook的状态被读取(第一次渲染期间是被初始化),之后,变量被改变为指向下一个Hook。 这也就是为什么总是始终保持Hook相同的调用顺序是很重要的。否则的话,状态值就会属于另一个状态变量。
总体来说,这儿有一个例子来一步步说明它是如何工作的:
React初始化Hook链表,并且用一个变量追踪当前Hook React首次调用你的组件 React发现了useState
的调用,创建了一个新的Hook对象(带有初始状态),将当前Hook变量指向该Hook对象,将该对象添加到Hooks链表中,然后返回一个带有初始值和更新状态方法的数组 React发现另一个useState
的调用,重复上面的步骤,存储新的hook对象,改变当前Hook变量。 组件状态发生变化 React给要处理的队列发送新的状态更新操作(执行useState
返回的方法) React决定组件是否需要重新渲染 React重置当前Hook变量,并且调用组件 React发现了一个useState
的调用,但此时,在Hooks链表第一位置已经有一个Hook,它仅仅改变了当前Hook变量,返回一个带状态值和更新状态方法的数组 React发现另一个useState
的调用,因为第二个位置有一个Hook了,它再次仅仅改变了当前Hook变量,返回一个带状态值和更新状态方法的数组 这是一个简单的useState工作流程,具体可以看ReactFiberHooks源码 useReducer
Hook的使用对于很多高级使用情况,我们可以使用useReducer
Hook来代替useState
。当处理复杂的状态逻辑时,它是很有用的。比如包含多个子值或者状态依赖之前的值。 Look,看下如何使用useReducer
Hook,示例 :
1 2 const [state, dispatch] = useReducer(reducer, initialArgument, init );
在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 代码来显示。下面对其主要属性和方法进行介绍。
主要属性 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 是默认开启的。
主要方法 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
时,可实现html
和rn
之间的通信。rn
向html
发送数据可以通过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 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 document .getElementsByTagName ('button' )[0 ].addEventListener ('click' , function ( ) { window .ReactNativeWebView .postMessage (JSON .stringify ({ message : "html发来的消息" })); });
当html
中调用了window.postMessage
函数后,WebView
的onMessage
函数将会被回调,用来处理html
向rn
发送的数据,可以通过e.nativeEvent.data
获取发送过来的数据。
1 2 3 4 5 6 7 8 9 _onH5Message = (e ) => { this .setState ({ messagesReceivedFromWebView : this .state .messagesReceivedFromWebView + 1 , message : e.nativeEvent .data , }) Alert .alert (e.nativeEvent .data ) }
JavaScript 脚本注入方式 注:这种方式适用于 react-native-webview
(RN
本身没有试过)。
在android
开发中,需要使用 javaScriptEnabled
属性来支持JavaScript
,ios
默认是支持的,没有此属性。在WebView
中提供了函数injectJavaScript(String)
,它有一个字符串参数,可以向webview
中注入脚本,如下:
1 2 3 4 5 6 7 8 injectJS = () => { const script = 'document.write("Injected JS ")' ; 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 ( ){ this .refs .webView .injectJavaScript (`receiveMessage("RN向H5发送消息");true;` ) }
H5端(Vue实现)
1 2 3 4 5 6 mounted ( ){ 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" ); } 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 })); };