一、受控组件
1.1 什么是受控组件?
受控组件:在 html 中,input、textarea、select 这种表单元素,这些元素可以根据用户的输入变更自身的状态(数据);而 React 中的可变状态放在 state 中,我们把两者结合起来,让 React 的 state 成为单一数据来源,这样的组件称为受控组件
说白了就是 表单元素的 value 绑定 state 里面的属性,然后当表单的 value 发生变化时更新 state 里面的数据,即受控组件就是受 state 控制的组件
React 是单向数据流,需要使用受控组件的方式来实现双向数据绑定
1.2 示例
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
class Input extends Component {
constructor () {
super()
this.state = {
val: 100
}
}
changeVal = (e) => {
// 事件函数中的 e 是事件源,获取 input 的 value
this.setState({
val: e.target.value
})
}
render () {
return (<div>
<input type="text" value={this.state.val} onChange={this.changeVal}/>
<p>{this.state.val}</p>
</div>)
}
}
ReactDOM.render(<Input />, document.querySelector('#root'))
二、非受控组件
2.1 什么是非受控组件?
非受控组件:不受 React 的状态控制的组件;受控组件的 value 存在 state 中,如果想获取从 state 当中获取就可以了;而非受控组件的 value 存在 DOM 元素上,此时需要使用 ref 获取表单元素的值。
2.2 ref
ref 是 React 提供的用来操作 DOM 的;使用 ref 需要创建 ref,然后在需要被获取的 DOM 对象上使用 ref 即可
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
class Sum extends Component {
constructor () {
super()
this.state = {
num: 0
}
this.myRefA = React.createRef() // 创建 ref,在构造函数中创建 ref
// this.myRefB = React.createRef()
}
changeState = () => {
// 使用 ref :当组件挂载后,通过 ref 属性的 current 属性访问该 DOM 元素
// this.myRefA.current 就是 第一个 input
// this.myRefB.current 就是 第二个 input
// console.log(this.myRefA.current)
let num = +this.myRefA.current.value + +this.myRefB.current.value;
this.setState({
num
})
}
render () {
return (<div onChange={this.changeState}>
{/*在 DOM 元素中使用 ref,ref 的值是上面创建的 ref*/}
<input type="text" ref={this.myRefA} /> <br/>
{/*ref 第二种形式,设置一个箭头函数,this.myRefB 就是第二个 input 的 ref,el 就是这个 input,这个函数会在组件挂载后执行,执行后在父组件的 this 上的 myRefB 就是这个 input 元素了*/}
<input type="text" ref={(el) => {this.myRefB = el} } />
<p>{this.state.num}</p>
</div>)
}
}
ReactDOM.render(<Sum />, document.getElementById('root'))
- 非受控组件通过 ref 获取 DOM 元素,然后通过 value 属性获取该表单元素的值
三、React 的生命周期
和 Vue 类似,React 的组件也有生命周期;但是只有通过 class 声明的组件才会有生命周期的钩子函数;
学习 React 的生命周期,我们主要学习以下钩子函数;
- constructor 构造函数
- componentWillMount 组件将要挂载
- render 渲染函数
- componentDidMount 组件已经挂载
- componentWillReceiveProps 初始化组件不会执行,当父组件数据发生变化,会触发这个钩子
- shouldComponentUpdate props 或 state 改变是否更新视图,默认更新
- componentWillUpdate 组件更新前调用
- componentDidUpdate 组件更新后调用,首次渲染不会执行此方法。
- componentWillUnmount 组件卸载及销毁之前直接调
示例
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
// 组件:
// 生命周期:生命周期的钩子函数;
class Parent extends Component {
// constructor 和 render 都是 react 的生命周期的钩子函数
constructor () {
super()
this.state = {
num: 0
}
console.log('constructor')
}
componentWillMount () {
// 组件第一次初始化,将要挂载,执行一次
console.log('componentWillMount')
}
componentDidMount () {
console.log('componentDidMount')
}
shouldComponentUpdate (nextProps, nextState) {
// 当组件初始化时,不会执行,只有当属性或者 state 发生改变时,才会执行这个函数;
// nextProps 代表改变之后的 props 对象
// nextState 代表改变之后的 state 对象
console.log('shouldComponentUpdata')
// 如果这个函数返回一个 false ,不会再次调用 render 方法,如果返回一个 true ,那么会继续调用 render ,改变视图
return nextState.num % 3
}
componentWillUpdate () {
console.log('componentWillUpdate')
}
componentDidUpdate () {
// 只能获取上一次更新的数据
// render 之后才会执行该方法
console.log('componentDidUpdate')
}
componentWillUnmount () {
// 当组件销毁执触发该钩子
// 移除定时器、移除事件监听器
console.log('componentWillUnmount')
}
handleClick = () => this.setState({num: this.state.num + 1})
render () {
console.log('render')
return (<div>
<p>
{this.state.num}
</p>
<Child n={this.state.num}></Child>
<button onClick={this.handleClick}>加加</button>
</div>)
}
}
// react 中的生命周期的钩子函数执行:defaultProps => constructor => componentWillMount => render => componentDidMount
// state 或 props 改变
// shouldComponentUpdate => componentWillUpdate => render => componentDidUpdate
// 当父组件触发 render 时,才会执行子组件中关于数据更新对应的钩子
class Child extends Component {
constructor () {
super()
console.log(100)
}
componentWillMount () {
console.log(200)
}
// 数据更新会触发子组件以下钩子函数:
// 初始化组件不会执行,当父组件数据发生变化,会触发这个钩子
componentWillReceiveProps (props) {
console.log('子组件:componentWillReceiveProps')
}
shouldComponentUpdate () {
console.log('子组件:shouldComponentUpdate')
return true
}
componentWillUpdate () {
// 当 shouldComponentUpdate 返回 true,才会执行这个函数
console.log('子组件 componentWillUpdate')
}
componentDidUpdate () {
console.log('子组件 componentDidUpdate')
}
render () {
return (<div>
{this.props.n}
</div>)
}
}
ReactDOM.render(<Parent />, document.getElementById('root'))
四、React 轮播图
本例使用 React 开发一个简单的轮播图;项目结构如下
│ index.css 项目的 css 文件
│ index.js slider 的入口文件
│
├─components 组件文件
│ Slider.js
│ SliderArrow.js 轮播图的左右箭头
│ SliderDots.js 轮播图的焦点
│ SliderItems.js 单个轮播图项
│
└─images 轮播图使用的图片
1.jpg
2.jpg
3.jpg
index.js
import React from 'react'
import ReactDOM from 'react-dom'
import Slider from './components/Slider'
import './index.css'
async function getImgs () {
let imgs = await Promise.all(
[
import('./images/1.jpg'),
import('./images/2.jpg'),
import('./images/3.jpg')
]
)
return imgs
}
getImgs().then(res => {
// console.log(res)
let data = res.map(i => i.default)
ReactDOM.render(<Slider images={data}/>, document.getElementById('root'))
})
index.css
* {
margin: 0;
padding: 0;
}
ul li {
list-style: none;
}
.container {
width: 500px;
height: 300px;
margin: 10px auto;
position: relative;
overflow: hidden;
}
.wrapper {
width: 1600px;
height: 300px;
position: absolute;
left: 0;
}
.wrapper .slider {
width: 500px;
height: 300px;
float: left;
}
.wrapper .slider img {
width: 100%;
height: 100%;
}
.slider-arrow {
position: absolute;
width: 100%;
height: 30px;
margin-top: -15px;
top: 50%;
}
.slider-arrow span {
display: inline-block;
width: 30px;
height: 30px;
line-height: 30px;
text-align: center;
background: #eee;
cursor: pointer;
}
.slider-arrow span.left {
float: left;
}
.slider-arrow span.right {
float: right;
}
.slider-dots {
position: absolute;
bottom: 10px;
width: 100%;
text-align: center;
}
.slider-dots span {
display: inline-block;
margin-right: 2px;
border-radius: 10px;
width: 20px;
height: 20px;
background: #eee;
}
.slider-dots span.active {
background: green;
}
components/Slider.js
// 负责 把组件都组装在一起使它们称为一个功能
import React, { Component } from 'react'
import SliderItems from './SliderItems'
import SliderArrow from './SliderArrow'
import SliderDots from './SliderDots'
export default class Slider extends Component {
constructor () {
super()
this.state = {
index: 0
}
}
go = () => {
this.timer = setInterval(() => {
this.turn(1)
}, 2000)
}
getUl = (el) => {
this.ulRef = el
}
turn = (step) => {
let index = this.state.index + step
if (index > this.props.images.length) {
// 越界:瞬间回到
this.ulRef.style.left = 0
this.ulRef.style.transitionDuration = '0s' // 清除动画
setTimeout(() => {
this.ulRef.style.transitionDuration = '0.5s'
index = 1
this.setState({index})
}, 0)
return // 因为设置了setTimeout 所以需要等待 setTimeout 后再设置最后状态
}
if (index < 0) {
this.ulRef.style.left = -1 * this.props.images.length * 500 + 'px'
this.ulRef.style.transitionDuration = '0s' // 清除动画
setTimeout(() => {
this.ulRef.style.transitionDuration = '0.5s'
index = this.props.images.length - 1
this.setState({index})
}, 0)
return
}
this.setState({
index
})
}
stop = () => {
window.clearInterval(this.timer)
}
componentDidMount () {
this.go()
}
render () {
return (<div className="container" onMouseOver={this.stop} onMouseOut={this.go}>
<SliderItems images={this.props.images} index={this.state.index} getUlRef={this.getUl} />
<SliderArrow turn={this.turn} />
<SliderDots images={this.props.images} index={this.state.index} turn={this.turn} />
</div>)
}
}
/components/SliderArrow.js
import React, { Component } from 'react'
export default class SliderArrow extends Component {
render () {
return ( <div className="slider-arrow">
<span className="left" onClick={() => this.props.turn(-1)}><</span>
<span className="right" onClick={() => this.props.turn(1)}>></span>
</div>)
}
}
/components/SliderDots.js
import React, { Component } from 'react'
export default class SliderDots extends Component {
render () {
// 轮播图下的焦点,需要根据当前轮播图正在展示的图片,给对应索引索引的小点增加选中样式,所以小点也需要 索引,此时 SliderDots 和 SliderItems 都需要 索引,所以应该使用把索引提升
let { props: { images } } = this
let cpImg = images.slice(0, 3)
return (<div className="slider-dots">
{
images.map((item, index) => {
if (index === 0 && this.props.index === images.length) {
return <span className={'active'} key={index} onClick={(e) => this.props.turn(index - this.props.index)}></span>
} else {
return <span className={index === this.props.index ? 'active' : '' } key={index} onClick={(e) => this.props.turn(index - this.props.index)}></span>
}
})
}
</div>)
}
}
/components/SliderItem.js
import React, { Component } from 'react'
export default class SliderItems extends Component {
constructor (props) {
super()
}
render () {
// 轮播图就是根据当前 container 要展示的 图片的索引,移动 wrapper,所以 wrapper 需要当前要展示图片的索引
// 根据当前轮播图展示图片的索引动态计算 wrapper 的 left 值
let style;
let { props: { index } } = this
style = {
left: -1 * index * 500 + 'px',
transition: 'all .5s linear 0s'
}
return (<ul className="wrapper" style={style} ref={this.props.getUlRef}>
{
this.props.images.map((item, index) => {
return (<li className="slider" key={index}>
<img src={item} alt=""/>
</li>)
})
}
<li className="slider">
<img src={this.props.images[0]} alt=""/>
</li>)
</ul>)
}
}
}