一、受控组件
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 的 valuethis.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.jpg2.jpg3.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 + stepif (index > this.props.images.length) {// 越界:瞬间回到this.ulRef.style.left = 0this.ulRef.style.transitionDuration = '0s' // 清除动画setTimeout(() => {this.ulRef.style.transitionDuration = '0.5s'index = 1this.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 - 1this.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 } } = thislet 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 } } = thisstyle = {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>)}}}
【发上等愿,结中等缘,享下等福,择高处立,寻平处住,向宽处行】
