第2章:React面向组件编程
2.1. 基本理解和使用
2.1.1. 使用React开发者工具调试
2.1.2. 效果
函数式组件:
2.1.3. 注意
- 组件名必须首字母大写
- 虚拟DOM元素只能有一个根元素
- 虚拟DOM元素必须有结束标签
function Demo() {
return <h2>自定义组件</h2>
}
const VDOM = (
<div>
<h2 className="title" id="{myId}">
<span style={{color:'white'}}>{myData}</span>
</h2>
<Demo></Demo>
</div>
)
ReactDOM.render(VDOM, document.getElementById('test'))
2.1.4. 渲染类组件标签的基本流程
- React内部会创建组件实例对象
- 调用render()得到虚拟DOM, 并解析为真实DOM
- 插入到指定的页面元素内部
// 1.创建类式组件
class MyConponent extends React.Component {
render() {
// render是放在哪里?MyConponent类的原型对象上,供实例使用
// render中的this是谁?MyConponent的实例对象<=>MyConponent组件实例对象
return <h2>复杂组件</h2>
}
}
// 2.渲染组件到页面
ReactDOM.render(<MyConponent/>, document.getElementById('test'))
/* 执行了ReactDOM.render(<MyConponent/>.....)之后,发生了什么?
1.React解析组件标签,找到了MyConponent组件
2.发现组件是使用类定义的,随后new出该类的实例,并通过该实例调用到原型上的render方法。
3.将render返回的虚拟DOM转为真实DOM,随后呈现在页面。
*/
2.2. 组件三大核心属性1: state
2.2.1. 效果
需求: 定义一个展示天气信息的组件
- 默认展示天气炎热 或 凉爽
- 点击文字切换天气
// 1.创建类式组件
class Weather extends React.Component {
constructor(props) {
super(props)
// 初始化状态
this.state = {
isHot: false,
wind: '小风'
}
// bind()帮助生成新的函数,解决changeWeather中this指向问题
this.changeWeather = this.changeWeather.bind(this)
}
render() {
console.log(this)
const {isHot, wind} = this.state
return <h2 onClick={this.changeWeather}>今天天气很{isHot?'炎热':'寒冷'},有点{wind}</h2>
}
changeWeather() {
// 由于changeWeaather是作为onClick的回调,所以不是通过实例调用的,是直接调用
// 类中的方法默认直接开起了局部的严格模式,所以changeWeather中的this为undefined
console.log(this)
// state状态不可以直接更改
const isHot = this.state.isHot
// 状态必须通过setState进行更新,且更新是一种合并,不是替换
this.setState({isHot: !isHot})
// this.state.isHot = !isHot // 错误写法
}
}
// 2.渲染组件到页面
ReactDOM.render(<Weather/>, document.getElementById('test'))
// 1.创建类式组件
class Weather2 extends React.Component {
constructor(props) {
super(props)
}
state = {
isHot: false,
wind: '小风'
}
render() {
const {isHot, wind} = this.state
return <h2 onClick={this.changeWeather}>今天天气很{isHot?'炎热':'寒冷'},有点{wind}</h2>
}
// 箭头函数没有自己的this,会往外找
// 自定义方法——用赋值语句的形式+箭头函数
changeWeather = ()=> {
const isHot = this.state.isHot
this.setState({isHot: !isHot})
}
}
// 2.渲染组件到页面
ReactDOM.render(<Weather2/>, document.getElementById('test'))
2.2.2. 理解
- state是组件对象最重要的属性, 值是对象(可以包含多个key-value的组合)
- 组件被称为"状态机", 通过更新组件的state来更新对应的页面显示(重新渲染组件)
2.2.3. 强烈注意
- 组件中render方法中的this为组件实例对象
- 组件自定义的方法中this为undefined,如何解决?
- 强制绑定this: 通过函数对象的bind()
- 箭头函数
- 状态数据,不能直接修改或更新
- 严格模式中的this不能指向window
2.3. 组件三大核心属性2: props
2.3.1. 效果(类式组件和函数式组件)
需求: 自定义用来显示一个人员信息的组件
- 姓名必须指定,且为字符串类型;
- 性别为字符串类型,如果性别没有指定,默认为男
- 年龄为字符串类型,且为数字类型,默认值为18
类式组件:
class Person extends React.Component {
// 对标签属性进行类型、必要性的限制
static propTypes = {
name: PropTypes.string.isRequired, // 限制name必传,且为字符串
sex: PropTypes.string, // 限制sex为字符串
age: PropTypes.number, // 限制age为数值
speak: PropTypes.func // 限制speak为函数
}
// 指定默认标签属性值
static defaultProps = {
sex: '男',
age: 18
}
render() {
const {name, sex, age} = this.props
// props是只读的
// this.props.name = 'jack' // 此行代码会报错
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age+1}</li>
</ul>
)
}
}
ReactDOM.render(<Person name="Tom" age={18} speak={speak}/>, document.getElementById('test'))
const p = {name:"Lily",sex:"女",age:18}
ReactDOM.render(<Person {...p}/>, document.getElementById('test2'))
function speak() {
console.log('我会说话')
}
函数式组件:
function Person(props){
const {name, sex, age} = props
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age+1}</li>
</ul>
)
}
// 对标签属性进行类型、必要性的限制
Person.propTypes = {
name: PropTypes.string.isRequired, // 限制name必传,且为字符串
sex: PropTypes.string, // 限制sex为字符串
age: PropTypes.number, // 限制age为数值
speak: PropTypes.func // 限制speak为函数
}
// 指定默认标签属性值
Person.defaultProps = {
sex: '男',
age: 18
}
ReactDOM.render(<Person name="Tom" age={18} speak={speak}/>, document.getElementById('test'))
const p = {name:"Lily",sex:"女",age:18}
ReactDOM.render(<Person {...p}/>, document.getElementById('test2'))
function speak() {
console.log('我会说话')
}
2.3.2. 理解
1.每个组件对象都会有props(properties的简写)属性
2.组件标签的所有属性都保存在props中
2.3.3. 作用
- 通过标签属性从组件外向组件内传递变化的数据
- 注意: 组件内部不要修改props数据
2.3.4. 编码操作
-
内部读取某个属性值
this.props.name
-
对props中的属性值进行类型限制和必要性限制
- 第一种方式(React v15.5 开始已弃用):
Person.propTypes = { name: React.PropTypes.string.isRequired, age: React.PropTypes.number }
- 第二种方式(新):使用prop-types库进限制(需要引入prop-types库)
Person.propTypes = { name: PropTypes.string.isRequired, age: PropTypes.number. }
-
扩展属性: 将对象的所有属性通过props传递
<Person {...person}/>
-
默认属性值:
Person.defaultProps = { age: 18, sex:'男' }
-
组件类的构造函数
constructor(props){ // 构造器是否接收props,是否传递给super,取决于:是否希望在构造器中通过this访问props super(props) console.log(props)//打印所有属性 }
2.4. 组件三大核心属性3: refs与事件处理
2.4.1. 效果
需求: 自定义组件, 功能说明如下:
- 点击按钮, 提示第一个输入框中的值
- 当第2个输入框失去焦点时, 提示这个输入框中的值
效果如下:
String类型的ref,过时了,以后可能会被废弃
class Demo2 extends React.Component{
// 展示左侧栏的数据
showData = ()=> {
const {input1} = this.refs
alert(input1.value)
}
// 展示右侧栏的数据
showData2 = ()=> {
const {input2} = this.refs
alert(input2.value)
}
render() {
return (
<div>
<input ref="input1" type="text"/>
<button onClick={this.showData}>点我提示左侧的数据</button>
<input ref="input2" onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>
</div>
)
}
}
ReactDOM.render(<Demo2/>, document.getElementById('test'));
回调函数形式的ref:
class Demo3 extends React.Component{
// 展示左侧栏的数据
showData = ()=> {
const {input1} = this
alert(input1.value)
}
// 展示右侧栏的数据
showData2 = ()=> {
const {input2} = this
alert(input2.value)
}
render() {
return (
<div>
<input ref={(currentNode)=>{this.input1 = currentNode}} type="text"/>
<button onClick={this.showData}>点我提示左侧的数据</button>
<input ref={c => this.input2 = c} onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>
</div>
)
}
}
ReactDOM.render(<Demo3/>, document.getElementById('test'));
createRef创建ref容器:最麻烦的一种,但是是React最推荐的
class Demo4 extends React.Component{
/*
React.createRef()调用后可以返回一个容器,该容器可以存储被ref所标识的节点,该容器是专人专用的
*/
myRef = React.createRef()
myRef2 = React.createRef()
showData = ()=> {
alert(this.myRef.current.value)
}
showData2 = ()=> {
alert(this.myRef2.current.value)
}
render() {
return (
<div>
<input ref={this.myRef} type="text"/>
<button onClick={this.showData}>点我提示左侧的数据</button>
<input ref={this.myRef2} onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>
</div>
)
}
}
ReactDOM.render(<Demo4/>, document.getElementById('test'));
2.4.2. 理解
组件内的标签可以定义ref属性来标识自己
2.4.3. 编码
-
字符串形式的ref
<input ref="input1"/>
-
回调形式的ref
<input ref={(c)=>{this.input1 = c}}
-
createRef创建ref容器
myRef = React.createRef() <input ref={this.myRef}/>
注意:不要过度使用ref
class Demo4 extends React.Component{
myRef = React.createRef()
showData = ()=> {
alert(this.myRef.current.value)
}
showData2 = (event)=> {
alert(event.target.value)
}
render() {
return (
<div>
<input ref={this.myRef} type="text"/>
<button onClick={this.showData}>点我提示左侧的数据</button>
<input onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>
</div>
)
}
}
ReactDOM.render(<Demo4/>, document.getElementById('test'));
2.4.4. 事件处理
- 通过onXxx属性指定事件处理函数(注意大小写)
- React使用的是自定义(合成)事件, 而不是使用的原生DOM事件————为了更好的兼容性
- React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)————为了高效
- 通过event.target得到发生事件的DOM元素对象
2.5. 收集表单数据
2.5.1. 效果
需求: 定义一个包含表单的组件
输入用户名密码后, 点击登录提示输入信息
非受控组件:
class Login extends React.Component {
handelSubmit = (event)=> {
event.preventDefault();
const {username,password} = this
alert(`你输入的账号是:${username.value},密码是:${password.value}`)
}
render() {
return (
<form action="" onSubmit={this.handelSubmit}>
用户名:<input ref={c=>this.username=c} type="text" name="username"/>
密码:<input ref={c=>this.password=c} type="password" name="password"/>
<button>登陆</button>
</form>
)
}
}
ReactDOM.render(<Login/>, document.getElementById('test'));
受控组件:
class Login2 extends React.Component {
state = {
username: '',
password: ''
}
handelSubmit = (event)=> {
event.preventDefault();
const {username,password} = this.state
alert(`你输入的账号是:${username},密码是:${password}`)
}
saveUsername = (event)=> {
this.setState({username: event.target.value})
}
savePassword = (event)=> {
this.setState({password: event.target.value})
}
render() {
return (
<form action="" onSubmit={this.handelSubmit}>
用户名:<input onChange={this.saveUsername} type="text" name="username"/>
密码:<input onChange={this.savePassword} type="password" name="password"/>
<button>登陆</button>
</form>
)
}
}
ReactDOM.render(<Login2/>, document.getElementById('test'));
优化:使用柯里化
class Login3 extends React.Component {
state = {
username: '',
password: ''
}
handelSubmit = (event)=> {
event.preventDefault();
const {username,password} = this.state
alert(`你输入的账号是:${username},密码是:${password}`)
}
/*
高阶函数:只要符合以下任意一个,就是高阶函数
1.若A函数,接收的参数的是一个函数,那么A函数就可以称之为高阶函数。
2.若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。
常见的高阶函数有:Promise、setTimeout、arr.map()等等
函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。
*/
saveFormData = (dataType)=> {
return (event) => {
console.log(event.target.value)
// obj[key] = value
this.setState({[dataType]: event.target.value})
}
}
render() {
return (
<form action="" onSubmit={this.handelSubmit}>
用户名:<input onChange={this.saveFormData('username')} type="text" name="username"/>
密码:<input onChange={this.saveFormData('password')} type="password" name="password"/>
<button>登陆</button>
</form>
)
}
}
ReactDOM.render(<Login3/>, document.getElementById('test'));
函数的柯里化:
function sum(a) {
return (b)=>{
return (c)=>{
return a+b+c
}
}
}
const result = sum(1)(2)(3)
console.log(result) // 6
不用柯里化:
class Login4 extends React.Component {
state = {
username: '',
password: ''
}
handelSubmit = (event)=> {
event.preventDefault();
const {username,password} = this.state
alert(`你输入的账号是:${username},密码是:${password}`)
}
saveFormData = (dataType,event)=> {
this.setState({[dataType]: event.target.value})
}
render() {
return (
<form action="" onSubmit={this.handelSubmit}>
用户名:<input onChange={event=>this.saveFormData('username',event)} type="text" name="username"/>
密码:<input onChange={event=>this.saveFormData('password',event)} type="password" name="password"/>
<button>登陆</button>
</form>
)
}
}
ReactDOM.render(<Login4/>, document.getElementById('test'));
2.5.2. 理解
包含表单的组件分类
- 受控组件
- 非受控组件
2.6. 组件的生命周期
2.6.1. 效果
需求:定义组件实现以下功能:
- 让指定的文本做显示 / 隐藏的渐变动画
- 从完全可见,到彻底消失,耗时2S
- 点击“不活了”按钮从界面中卸载组件
class Life extends React.Component {
state = {opacity:0.5}
death = ()=> {
// 卸载组件
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
// 组件挂载完毕
componentDidMount() {
this.timer = setInterval(() => {
let {opacity} = this.state
opacity -= 0.1
if(opacity <= 0) opacity = 1
this.setState({opacity})
}, 200);
}
// 组件将要被卸载
componentWillUnmount() {
clearInterval(this.timer)
}
// render调用的时机:初始化渲染、状态更新之后
render() {
return (
<div>
<h2 style={{opacity:this.state.opacity}}>学不会怎么办</h2>
<button onClick={this.death}>不活了</button>
</div>
)
}
}
ReactDOM.render(<Life/>, document.getElementById('test'));
2.6.2. 理解
- 组件从创建到死亡它会经历一些特定的阶段。
- React组件中包含一系列勾子函数(生命周期回调函数), 会在特定的时刻调用。
- 我们在定义组件时,会在特定的生命周期回调函数中,做特定的工作。
- 生命周期回调函数<=>生命周期钩子函数<=>生命周期函数<=>生命周期钩子
2.6.3. 生命周期流程图(旧)
生命周期的三个阶段(旧)
-
初始化阶段: 由ReactDOM.render()触发—初次渲染
- constructor()
- componentWillMount()
- render()
- componentDidMount() ===> 常用 一般在这个钩子做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
-
更新阶段: 由组件内部this.setSate()或父组件重新render触发
- shouldComponentUpdate()
- componentWillUpdate()
- render()
- componentDidUpdate()
-
卸载组件: 由ReactDOM.unmountComponentAtNode()触发
- componentWillUnmount() ===> 常用 一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息
2.6.4. 生命周期流程图(新)
生命周期的三个阶段(新)
-
初始化阶段: 由ReactDOM.render()触发—初次渲染
- constructor()
- getDerivedStateFromProps ==> 从props得到一个派生的状态,使用场景比较少。若state的值在任何时候都取决于props,那么可以使用
getDerivedStateFromProps 会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回 null 则不更新任何内容。
static getDerivedStateFromProps(props) { return props }
- render()
- componentDidMount()
-
更新阶段: 由组件内部this.setSate()或父组件重新render触发
- getDerivedStateFromProps
- shouldComponentUpdate()
- render()
- getSnapshotBeforeUpdate ==> 在更新前获得快照,任何值都可以作为快照值
getSnapshotBeforeUpdate() 在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。此生命周期的任何返回值将作为参数传递给 componentDidUpdate()。
class NewList extends React.Component { state = {newsArr:[]} componentDidMount() { setInterval(()=>{ // 获取原状态 const {newsArr} = this.state const news = '新闻' + (newsArr.length+1) this.setState({newsArr: [news,...newsArr]}) },1000) } getSnapshotBeforeUpdate() { return this.refs.list.scrollHeight } componentDidUpdate(preProps, preState, height) { this.refs.list.scrollTop += this.refs.list.scrollHeight - height } render() { return ( <div className="list" ref="list"> { this.state.newsArr.map((n,index)=> { return <div className="news" key={index}>{n}</div> }) } </div> ) } } ReactDOM.render(<NewList/>, document.getElementById('test'));
- componentDidUpdate()
-
卸载组件: 由ReactDOM.unmountComponentAtNode()触发
- componentWillUnmount()
2.6.5. 重要的勾子
- render:初始化渲染或更新渲染调用
- componentDidMount:开启监听, 发送ajax请求
- componentWillUnmount:做一些收尾工作, 如: 清理定时器
2.6.6. 即将废弃的勾子
- componentWillMount
- componentWillReceiveProps
- componentWillUpdate
现在使用会出现警告,下一个大版本需要加上UNSAFE_前缀才能使用,以后可能会被彻底废弃,不建议使用。