文章略长,耐心读完,受益匪浅哦~
目录
前言
简介
JSX
面向组件编程
state
props
refs
组件生命周期
前言
简介
React框架由Facebook开发,和Vue框架一样,都是用于构建用户界面的JavaScript库;
它有如下三个特点:
- 采用组件化模式,声明式编程,提高开发效率及组件复用率
- 在React Native中可以使用React语法进行移动端开发
- 使用虚拟DOM和优秀的Diffing算法,尽量减少与真实DOM的交互;
如下是引入react的简单尝试:
// test.html
<body>
<!-- 引入react核心库 -->
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<!-- 引入react-dom库,用于支持react操作DOM -->
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<!-- 引入babel,用于将jsx解析为js -->
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<div id="test"></div>
<!-- 记住这里的类型是text/babel,下面写的是jsx语法 -->
<script type="text/babel">
// 创建虚拟DOM
const VDOM = (
<div>
<h1>React初尝试</h1>
</div>
)
ReactDOM.render(VDOM, document.getElementById('test')) // 渲染虚拟DOM到页面
</script>
</body>
JSX
JSX(JavaScript XML)是react定义的一种类似于XML的js扩展语法,用于简化创建虚拟DOM的过程;下面列出一些简单语法:
- 定义虚拟DOM时,不要写引号;
- 标签中混入JS表达式时要用{ };
- 样式的类名指定要用className;
- 内联样式,要用style={{key: value;...;}}的方式,其中最外层{}表示是js表达式,内存{}表示个对象;
- 样式的名称要用小驼峰命名,如fontSize
- 只有一个根标签
- 标签必须闭合
- 标签首字母若为小写,则会将标签转为html中同名标签;若首字母为大写,则意味着是个组件
在使用中,利用babel将jsx编译成js使用;
面向组件编程
在react中,组件分为函数式组件和类式组件;
- 函数式组件
函数式组件即为在创建虚拟DOM时使用函数定义;
// test.html
<body>
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<div id="test"></div>
<script type="text/babel">
// 函数式组件,名为test组件
function Test() {
console.log(this) // undefined,babel编译后开启了严格模式
// 返回虚拟DOM
return (
<div>
<h1>React初尝试</h1>
</div>
)
}
ReactDOM.render(<Test/>, document.getElementById('test')) // 渲染test组件到页面
</script>
</body>
执行ReactDOM.render之后,发生了什么?
- React解析组件标签,找到Test组件
- 发现Test组件是用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后渲染到页面上
在无hook之前,函数式组件中只有props这一种属性;
- 类式组件
类式组件即为在创建虚拟DOM时使用类定义;
// test.html
<body>
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<div id="test"></div>
<script type="text/babel">
// 创建类式组件,皆要继承于React.Component
class Demo extends React.Component {
render() {
return (
<div>
<h1>React初尝试</h1>
</div>
)
}
}
ReactDOM.render(<Demo />, document.getElementById('test')) // 渲染虚拟DOM到页面
</script>
</body>
其中render函数是定义在Demo组件的原型对象对象上,供实例使用;
执行了ReactDom.render之后发生了什么?
- React解析组件标签,找到Demo组件;
- 发现组件是类式组件,随后React自动new出来该类的实例,并通过该实例调用原型上的render方法;
- 将render方法返回的虚拟DOM转为真实DOM,随后渲染到页面上
类式组件上有三大属性:state,props,refs,下面分别进行介绍;
state
state是由多个key-value组成的对象;通过更新组件的state来更新页面(重新渲染页面);
state中的数据不能被直接更改,要通过调用setState({key: value})方法来更改;
如下案例为通过改变state中的workDay属性渲染页面显示工作日/休息日:
<body>
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<div id="test"></div>
<script type="text/babel">
class Demo extends React.Component {
// 构造函数传参为props,在react中创建类组件时,若写构造函数,则要写super,否则会引起内部bug
constructor(props) {
super(props)
this.state = {workDay: true} // 设置状态
// 未避免调用changeDay方法时this指向为undefined,因此利用bind将this指向变为实例对象,并将改变this指向后的函数赋值给实例对象的change属性
this.change = this.changeDay.bind(this)
}
render() {
return (
<div>
<h1 onClick={this.change}>今天是{this.state.workDay? '工作日': '休息日'}</h1>
</div>
)
}
// 该方法原本是给Demo类的实例对象使用的,若作为事件的回调函数,则this指向为undefined(babel为严格模式)
changeDay() {
this.setState({workDay: !this.state.workDay}) // 要利用setState方法修改state值
}
}
ReactDOM.render(<Demo />, document.getElementById('test')) // 渲染虚拟DOM到页面
</script>
</body>
如注释所说,若利用类组件方法创建组件,若在类中写了构造函数,则接收props参数的同时要写上super(props),以防止报错~
在构造函数中可以创建state对象,用以控制页面显示~
在定义事件函数时,若写在类中作为普通函数(则该方法如changeDay,只有作为类的实例对象被调用时,this才有值,即为该实例对象),作为事件被调用时,此时this为undefined。为解决此问题,可以在构造函数中改变this指向并赋值给另一属性,则此时的this就是类的实例对象,则可以正常被使用~
可以看到Demo实例上有change方法,原型对象上有changeDay方法;
备注:此处看不懂的要去复习ES6哦 JavaScript高级教程(面向对象编程)_迷糊的小小淘的博客-CSDN博客
修改state属性时,不能采用直接赋值的形式,要调用原型对象中的方法setState实现;
当然,state方法还有简单写法(不通过构造函数的方式),因为在继承类中,可省略构造函数constructor,同时若在类中直接写赋值语句,也可以将该属性给每个实例对象使用~所以可以将state直接写在类中,将事件回调函数用属性赋值的方式进行定义;
<script>
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
workerType = '程序媛'
say = function(){
console.log(`我的工作是${this.workerType}`)
}
}
let person = new Person('人类', 100)
console.log(person)
person.say()
</script>
因此用在此例中如下:
<script type="text/babel">
// 创建类式组件,皆要继承于React.Component
class Demo extends React.Component {
state = {workDay: true} // 设置状态
change = () => {
console.log(this); // 此处一定要写成箭头函数形式, this的指向才为实例对象
this.setState({workDay: !this.state.workDay})
}
render() {
return (
<div>
<h1 onClick={this.change}>今天是{this.state.workDay? '工作日': '休息日'}</h1>
</div>
)
}
}
ReactDOM.render(<Demo />, document.getElementById('test')) // 渲染虚拟DOM到页面
</script>
关于state的总结如下:
- state可定义在构造函数中,也可利用赋值语句方法赋值;
- state不可直接修改,需要调用原型对象中的setState方法;
- 类组件中的render方法this指向组件实例对象(由react自动创建出来),组件初始化时会被渲染一次,随后在每次组件更新时被调用;
- 类组件中的构造函数只会被调用一次;
- 类组件中普通函数定义改变this指向有两种方法:
① 在构造函数中利用bind等方法改变this指向;
② 利用箭头函数+函数赋值的方式
props
prop用于存放组件中的标签属性;props用于存放所有组件中的标签属性;
<script type="text/babel">
class Demo extends React.Component {
render() {
return (
<ul>
<li>{this.props.name}</li>
<li>{this.props.age}</li>
<li>{this.props.grade}</li>
</ul>
)
}
}
ReactDOM.render(<Demo name="小红" age="18" grade="100"/>, document.getElementById('test')) // 渲染虚拟DOM到页面
const properties = {name: '小明', age: '19', grade: "90"}
ReactDOM.render(<Demo {...properties}/>, document.getElementById('test1')) // 渲染虚拟DOM到页面
</script>
复用组件时,利用props可传入不同的标签属性用于展示~
可以限制属性的类型,默认值及是否必输特性,需引入prop-types库;通过给类组件的propTypes和defaultProps进行赋值;
注意:
自 React v15.5 起,
React.PropTypes
已移入另一个包中。请使用 prop-types 库 代替。
<body>
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<!-- 引入prop-types库 -->
<script src="https://unpkg.com/prop-types@15.6/prop-types.js"></script>
<div id="test"></div>
<div id="test1"></div>
<script type="text/babel">
class Demo extends React.Component {
render() {
const {name, age, grade} = this.props
return (
<ul>
<li>{name}</li>
<li>{age}</li>
<li>{grade}</li>
</ul>
)
}
}
Demo.propTypes = {
name: PropTypes.string.isRequired, // 设置name为字符串类型且为必输项
age: PropTypes.number, // 设置age为数字类型
grade: PropTypes.number // 设置grade为数字类型
}
Demo.defaultProps = {
age: 18, // 设置age默认值为18
grade: 90 // 设置grade默认值为90
}
ReactDOM.render(<Demo name="小红" grade={100}/>, document.getElementById('test')) // 渲染虚拟DOM到页面
const properties = {name: '小明', age: 19}
ReactDOM.render(<Demo {...properties}/>, document.getElementById('test1')) // 渲染虚拟DOM到页面
</script>
</body>
当然,也可以通过static关键字对其进行限制;
<script type="text/babel">
class Demo extends React.Component {
static propTypes = {
name: PropTypes.string.isRequired, // 设置name为字符串类型且为必输项
age: PropTypes.number, // 设置age为数字类型
grade: PropTypes.number // 设置grade为数字类型
}
static defaultProps = {
age: 18, // 设置age默认值为18
grade: 90 // 设置grade默认值为90
}
render() {
const {name, age, grade} = this.props
return (
<ul>
<li>{name}</li>
<li>{age}</li>
<li>{grade}</li>
</ul>
)
}
}
ReactDOM.render(<Demo name="小红" grade={100}/>, document.getElementById('test')) // 渲染虚拟DOM到页面
const properties = {name: '小明', age: 19}
ReactDOM.render(<Demo {...properties}/>, document.getElementById('test1')) // 渲染虚拟DOM到页面
</script>
函数式组件也可以使用props,通过给函数传参的方式拿到props:
<body>
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<!-- 引入prop-types库 -->
<script src="https://unpkg.com/prop-types@15.6/prop-types.js"></script>
<div id="test"></div>
<div id="test1"></div>
<script type="text/babel">
function Demo(props) {
const {name, age, grade} = props
return <ul>
<li>{name}</li>
<li>{age}</li>
<li>{grade}</li>
</ul>
}
Demo.propTypes = {
name: PropTypes.string.isRequired, // 设置name为字符串类型且为必输项
age: PropTypes.number, // 设置age为数字类型
grade: PropTypes.number // 设置grade为数字类型
}
Demo.defaultProps = {
age: 18, // 设置age默认值为18
grade: 90 // 设置grade默认值为90
}
ReactDOM.render(<Demo name="小红" grade={100}/>, document.getElementById('test')) // 渲染虚拟DOM到页面
const properties = {name: '小明', age: 19}
ReactDOM.render(<Demo {...properties}/>, document.getElementById('test1')) // 渲染虚拟DOM到页面
</script>
</body>
对props的总结如下:
- 组件标签的所有属性都保存在props中
- props属性是可读的,不能对其修改(会报错)
- 可以利用propTypes以及defaultProps对属性类型、是否必输、默认值进行设置
refs
利用refs来表示某标签节点本身,避免操作dom获得标签;有三种形式:
- 通过字符串赋值方式
<input ref="input">{name}</input>
<script type="text/babel">
class Demo extends React.Component {
state = {age: 17}
addAge = () => {
const newage = Number(this.refs.span.innerText) + 1 // 利用this.refs.span可以拿到span节点
this.setState({age: newage})
}
render() {
return (
<div>
<span ref="span">{this.state.age}</span><br/>
<button onClick={this.addAge}>加一</button>
</div>
)
}
}
ReactDOM.render(<Demo name="小红" age={10}/>, document.getElementById('test')) // 渲染虚拟DOM到页面
</script>
该方法使用简单,但是要少用(有bug,以后可能会被弃用);
- 回调函数方式的ref定义
可以在定义ref使用回调函数,传入的参数恰恰是该节点本身;将该参数赋值给其它变量获取即可;
<input ref={ (ele) => {this.input1 = ele} }>{name}</input>
<script type="text/babel">
class Demo extends React.Component {
state = {age: 17}
addAge = () => {
const newage = Number(this.span.innerText) + 1 // 利用this.refs.span可以拿到span节点
this.setState({age: newage})
}
render() {
return (
<div>
<span ref={(ele) => {this.span = ele}}>{this.state.age}</span><br/>
<button onClick={this.addAge}>加一</button>
</div>
)
}
}
ReactDOM.render(<Demo name="小红" age={10}/>, document.getElementById('test')) // 渲染虚拟DOM到页面
</script>
利用回调函数方式定义ref时,在更新过程中会被执行两次:一次传入参数为null,第二次才是正常的节点元素,这是因为在每次更新时会创建一个新的函数实例,所以React清空旧的ref并设置新的,当然该差别在开发中没啥影响;见官网说明:Refs and the DOM – React
-
createRef创建ref容器
Refs是使用React.createRef()创建的,并通过ref属性附加到React元素。在构造组件时,通过将Refs分配给实例属性,以便可以在整个组件中引用它们;创建一个标记一个,React.createRef()与标签是一对一关系;使用时须通过.current拿到该节点元素;
<script type="text/babel">
class Demo extends React.Component {
state = {age: 17, age1: 19}
myRef = React.createRef() // 创建标记加的span节点
myRef1 = React.createRef() // 创建标记减的span节点
addAge = () => {
const newage = Number(this.myRef.current.innerText) + 1 // 利用this.myRef.current可以拿到span节点
this.setState({age: newage})
}
subAge = () => {
const newage = Number(this.myRef1.current.innerText) - 1 // 利用this.myRef1.current可以拿到span节点
this.setState({age1: newage})
}
render() {
return (
<div>
<span ref={this.myRef}>{this.state.age}</span><br/>
<button onClick={this.addAge}>加一</button><br/>
<span ref={this.myRef1}>{this.state.age1}</span><br/>
<button onClick={this.subAge}>减一</button>
</div>
)
}
}
ReactDOM.render(<Demo />, document.getElementById('test')) // 渲染虚拟DOM到页面
</script>
对于refs的总结:
有三种方式使用refs
- 字符串形式的ref
<input ref="input">{name}</input>
- 回调形式的ref
<input ref={ (ele) => {this.input1 = ele} }>{name}</input>
- 使用React.createRef()创建
myRef = React.createRef()
<input ref={ this.myRef }></input>
节点元素需通过this.myRef.current拿到;
在开发中,要尽量减少对ref的使用,可以通过event.target拿到发生事件的DOM元素;
组件生命周期
组件生命周期是指组件对象从创建到死亡会经历一些特定阶段。又称为组件生命钩子;
图片来源React lifecycle methods diagram
上图展示了reactV16.4以后所有的生命周期函数,
- 组件挂载时,依次会执行类的构造函数construtor、getDerivedStateFromProps、render函数及componentDidMount
组件挂载通过ReactDOM.render()触发;
static getDerivedStateFromProps(props, state)
getDerivedStateFromProps会在调用render方法之前被调用,它应返回一个对象来更新state,若返回null则表示不更新任何内容(不写该函数表示返回null)。该函数适用于state的值在任何时候都取决于props。
componentDidMount()方法会在组件挂载后(插入到DOM树中)立即调用。一般在此阶段发送网络请求、开启定时器、订阅消息等;
<script type="text/babel">
class Demo extends React.Component {
state = {age: 17}
myRef = React.createRef() // 创建标记加的span节点
addAge = () => {
const newage = Number(this.myRef.current.innerText) + 1 // 利用this.myRef.current可以拿到span节点
this.setState({age: newage})
}
render() {
return (
<div>
<span ref={this.myRef}>{this.state.age}</span><br/>
<button onClick={this.addAge}>加一</button><br/>
</div>
)
}
static getDerivedStateFromProps() {
console.log('getDerivedStateFromProps')
return {age: 21}
// 此处返回state对象中的age为21时,则不论点多少次按钮,age都不会被改变; 若返回null,则age会被修改
}
componentDidMount() {
console.log('componentDidMount') // 在此处设置定时器,发送请求,订阅消息等
}
}
ReactDOM.render(<Demo />, document.getElementById('test')) // 渲染虚拟DOM到页面
</script>
- 组件更新时,依次执行getDerivedStateFromProps、shouldComponentUpdate、render、getSnapShotBeforeUpdate、componentDidUpdate
组件更新由组件内部setState或者父组件更新触发;
shouldComponentUpdate()返回布尔值,用以判断React组件的输出是否受当前state或props更改的影响,默认值是true,表示state每次发生变化组件都会重新渲染;该方法在首次渲染或使用forceUpdate()时不会被调用;但是官网建议慎用哦~
此方法仅作为性能优化的方式而存在。不要企图依靠此方法来“阻止”渲染,因为这可能会产生 bug。
getSnapshotBeforeUpdate(prevProps, prevState)
getSnapshotBeforeUpdate()方法在最近一次渲染输出(提交到DOM节点)之前调用。它使得组件能在发生更改之前从DOM中捕获一些信息,如滚动位置。此方法的任何返回值都将作为参数传递给componentDidUpdate();该方法并不常用;
componentDidUpdate(prevProps, prevState, snapshot)
componentDidUpdate()方法会在组件更新后立即调用。首次渲染不会执行该方法。如果组件实现了getSnapshotBeforeUpdate()方法,则它的返回值将作为第三个参数传递给componentDidUpdate(),否则该参数为undefined;
<script type="text/babel">
class Demo extends React.Component {
state = {age: 17}
myRef = React.createRef() // 创建标记加的span节点
addAge = () => {
const newage = Number(this.myRef.current.innerText) + 1 // 利用this.myRef.current可以拿到span节点
this.setState({age: newage})
}
render() {
return (
<div>
<span ref={this.myRef}>{this.state.age}</span><br/>
<button onClick={this.addAge}>加一</button><br/>
</div>
)
}
static getDerivedStateFromProps(props, state) {
console.log('getDerivedStateFromProps')
return props
}
componentDidMount() {
console.log('componentDidMount')
}
shouldComponentUpdate() {
console.log('shouldComponentUpdate')
return true
// 此处设为false则点击按钮不会被更新,下面两个方法就不会被执行,点击true则正常更新
}
getSnapshotBeforeUpdate(prevProps, prevState) {
console.log('getSnapshotBeforeUpdate')
return 123
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('componentDidUpdate')
return 456
}
}
ReactDOM.render(<Demo />, document.getElementById('test')) // 渲染虚拟DOM到页面
</script>
- 组件卸载时,执行componentWillUnmount()
组件卸载通过ReactDOM.unmountComponentAtNode触发;
componentWillUnmount()用在组件卸载及销毁之前直接调用,一般在此阶段执行必要的清理操作,如清除定时器、取消网络请求或清除在componentDidMount()中创建的订阅;
下图展示了常用的声明周期函数,在日常开发中经常被使用。
图片来源React lifecycle methods diagram
备注:旧版本中有几个弃用的声明周期函数,如 componentWillUnmount() 、componentWillUpdate() 、componentWillReceiveProps(),了解即可~
To be continue~