一、受控组件

1.1 什么是受控组件?

受控组件:在 html 中,input、textarea、select 这种表单元素,这些元素可以根据用户的输入变更自身的状态(数据);而 React 中的可变状态放在 state 中,我们把两者结合起来,让 React 的 state 成为单一数据来源,这样的组件称为受控组件

说白了就是 表单元素的 value 绑定 state 里面的属性,然后当表单的 value 发生变化时更新 state 里面的数据,即受控组件就是受 state 控制的组件

React 是单向数据流,需要使用受控组件的方式来实现双向数据绑定

1.2 示例

  1. import React, { Component } from 'react'
  2. import ReactDOM from 'react-dom'
  3. class Input extends Component {
  4. constructor () {
  5. super()
  6. this.state = {
  7. val: 100
  8. }
  9. }
  10. changeVal = (e) => {
  11. // 事件函数中的 e 是事件源,获取 input 的 value
  12. this.setState({
  13. val: e.target.value
  14. })
  15. }
  16. render () {
  17. return (<div>
  18. <input type="text" value={this.state.val} onChange={this.changeVal}/>
  19. <p>{this.state.val}</p>
  20. </div>)
  21. }
  22. }
  23. 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 即可

  1. import React, { Component } from 'react'
  2. import ReactDOM from 'react-dom'
  3. class Sum extends Component {
  4. constructor () {
  5. super()
  6. this.state = {
  7. num: 0
  8. }
  9. this.myRefA = React.createRef() // 创建 ref,在构造函数中创建 ref
  10. // this.myRefB = React.createRef()
  11. }
  12. changeState = () => {
  13. // 使用 ref :当组件挂载后,通过 ref 属性的 current 属性访问该 DOM 元素
  14. // this.myRefA.current 就是 第一个 input
  15. // this.myRefB.current 就是 第二个 input
  16. // console.log(this.myRefA.current)
  17. let num = +this.myRefA.current.value + +this.myRefB.current.value;
  18. this.setState({
  19. num
  20. })
  21. }
  22. render () {
  23. return (<div onChange={this.changeState}>
  24. {/*在 DOM 元素中使用 ref,ref 的值是上面创建的 ref*/}
  25. <input type="text" ref={this.myRefA} /> <br/>
  26. {/*ref 第二种形式,设置一个箭头函数,this.myRefB 就是第二个 input 的 ref,el 就是这个 input,这个函数会在组件挂载后执行,执行后在父组件的 this 上的 myRefB 就是这个 input 元素了*/}
  27. <input type="text" ref={(el) => {this.myRefB = el} } />
  28. <p>{this.state.num}</p>
  29. </div>)
  30. }
  31. }
  32. 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 组件卸载及销毁之前直接调

示例

  1. import React, { Component } from 'react'
  2. import ReactDOM from 'react-dom'
  3. // 组件:
  4. // 生命周期:生命周期的钩子函数;
  5. class Parent extends Component {
  6. // constructor 和 render 都是 react 的生命周期的钩子函数
  7. constructor () {
  8. super()
  9. this.state = {
  10. num: 0
  11. }
  12. console.log('constructor')
  13. }
  14. componentWillMount () {
  15. // 组件第一次初始化,将要挂载,执行一次
  16. console.log('componentWillMount')
  17. }
  18. componentDidMount () {
  19. console.log('componentDidMount')
  20. }
  21. shouldComponentUpdate (nextProps, nextState) {
  22. // 当组件初始化时,不会执行,只有当属性或者 state 发生改变时,才会执行这个函数;
  23. // nextProps 代表改变之后的 props 对象
  24. // nextState 代表改变之后的 state 对象
  25. console.log('shouldComponentUpdata')
  26. // 如果这个函数返回一个 false ,不会再次调用 render 方法,如果返回一个 true ,那么会继续调用 render ,改变视图
  27. return nextState.num % 3
  28. }
  29. componentWillUpdate () {
  30. console.log('componentWillUpdate')
  31. }
  32. componentDidUpdate () {
  33. // 只能获取上一次更新的数据
  34. // render 之后才会执行该方法
  35. console.log('componentDidUpdate')
  36. }
  37. componentWillUnmount () {
  38. // 当组件销毁执触发该钩子
  39. // 移除定时器、移除事件监听器
  40. console.log('componentWillUnmount')
  41. }
  42. handleClick = () => this.setState({num: this.state.num + 1})
  43. render () {
  44. console.log('render')
  45. return (<div>
  46. <p>
  47. {this.state.num}
  48. </p>
  49. <Child n={this.state.num}></Child>
  50. <button onClick={this.handleClick}>加加</button>
  51. </div>)
  52. }
  53. }
  54. // react 中的生命周期的钩子函数执行:defaultProps => constructor => componentWillMount => render => componentDidMount
  55. // state 或 props 改变
  56. // shouldComponentUpdate => componentWillUpdate => render => componentDidUpdate
  57. // 当父组件触发 render 时,才会执行子组件中关于数据更新对应的钩子
  58. class Child extends Component {
  59. constructor () {
  60. super()
  61. console.log(100)
  62. }
  63. componentWillMount () {
  64. console.log(200)
  65. }
  66. // 数据更新会触发子组件以下钩子函数:
  67. // 初始化组件不会执行,当父组件数据发生变化,会触发这个钩子
  68. componentWillReceiveProps (props) {
  69. console.log('子组件:componentWillReceiveProps')
  70. }
  71. shouldComponentUpdate () {
  72. console.log('子组件:shouldComponentUpdate')
  73. return true
  74. }
  75. componentWillUpdate () {
  76. // 当 shouldComponentUpdate 返回 true,才会执行这个函数
  77. console.log('子组件 componentWillUpdate')
  78. }
  79. componentDidUpdate () {
  80. console.log('子组件 componentDidUpdate')
  81. }
  82. render () {
  83. return (<div>
  84. {this.props.n}
  85. </div>)
  86. }
  87. }
  88. ReactDOM.render(<Parent />, document.getElementById('root'))

四、React 轮播图

本例使用 React 开发一个简单的轮播图;项目结构如下

  1. index.css 项目的 css 文件
  2. index.js slider 的入口文件
  3. ├─components 组件文件
  4. Slider.js
  5. SliderArrow.js 轮播图的左右箭头
  6. SliderDots.js 轮播图的焦点
  7. SliderItems.js 单个轮播图项
  8. └─images 轮播图使用的图片
  9. 1.jpg
  10. 2.jpg
  11. 3.jpg

index.js

  1. import React from 'react'
  2. import ReactDOM from 'react-dom'
  3. import Slider from './components/Slider'
  4. import './index.css'
  5. async function getImgs () {
  6. let imgs = await Promise.all(
  7. [
  8. import('./images/1.jpg'),
  9. import('./images/2.jpg'),
  10. import('./images/3.jpg')
  11. ]
  12. )
  13. return imgs
  14. }
  15. getImgs().then(res => {
  16. // console.log(res)
  17. let data = res.map(i => i.default)
  18. ReactDOM.render(<Slider images={data}/>, document.getElementById('root'))
  19. })

index.css

  1. * {
  2. margin: 0;
  3. padding: 0;
  4. }
  5. ul li {
  6. list-style: none;
  7. }
  8. .container {
  9. width: 500px;
  10. height: 300px;
  11. margin: 10px auto;
  12. position: relative;
  13. overflow: hidden;
  14. }
  15. .wrapper {
  16. width: 1600px;
  17. height: 300px;
  18. position: absolute;
  19. left: 0;
  20. }
  21. .wrapper .slider {
  22. width: 500px;
  23. height: 300px;
  24. float: left;
  25. }
  26. .wrapper .slider img {
  27. width: 100%;
  28. height: 100%;
  29. }
  30. .slider-arrow {
  31. position: absolute;
  32. width: 100%;
  33. height: 30px;
  34. margin-top: -15px;
  35. top: 50%;
  36. }
  37. .slider-arrow span {
  38. display: inline-block;
  39. width: 30px;
  40. height: 30px;
  41. line-height: 30px;
  42. text-align: center;
  43. background: #eee;
  44. cursor: pointer;
  45. }
  46. .slider-arrow span.left {
  47. float: left;
  48. }
  49. .slider-arrow span.right {
  50. float: right;
  51. }
  52. .slider-dots {
  53. position: absolute;
  54. bottom: 10px;
  55. width: 100%;
  56. text-align: center;
  57. }
  58. .slider-dots span {
  59. display: inline-block;
  60. margin-right: 2px;
  61. border-radius: 10px;
  62. width: 20px;
  63. height: 20px;
  64. background: #eee;
  65. }
  66. .slider-dots span.active {
  67. background: green;
  68. }

components/Slider.js

  1. // 负责 把组件都组装在一起使它们称为一个功能
  2. import React, { Component } from 'react'
  3. import SliderItems from './SliderItems'
  4. import SliderArrow from './SliderArrow'
  5. import SliderDots from './SliderDots'
  6. export default class Slider extends Component {
  7. constructor () {
  8. super()
  9. this.state = {
  10. index: 0
  11. }
  12. }
  13. go = () => {
  14. this.timer = setInterval(() => {
  15. this.turn(1)
  16. }, 2000)
  17. }
  18. getUl = (el) => {
  19. this.ulRef = el
  20. }
  21. turn = (step) => {
  22. let index = this.state.index + step
  23. if (index > this.props.images.length) {
  24. // 越界:瞬间回到
  25. this.ulRef.style.left = 0
  26. this.ulRef.style.transitionDuration = '0s' // 清除动画
  27. setTimeout(() => {
  28. this.ulRef.style.transitionDuration = '0.5s'
  29. index = 1
  30. this.setState({index})
  31. }, 0)
  32. return // 因为设置了setTimeout 所以需要等待 setTimeout 后再设置最后状态
  33. }
  34. if (index < 0) {
  35. this.ulRef.style.left = -1 * this.props.images.length * 500 + 'px'
  36. this.ulRef.style.transitionDuration = '0s' // 清除动画
  37. setTimeout(() => {
  38. this.ulRef.style.transitionDuration = '0.5s'
  39. index = this.props.images.length - 1
  40. this.setState({index})
  41. }, 0)
  42. return
  43. }
  44. this.setState({
  45. index
  46. })
  47. }
  48. stop = () => {
  49. window.clearInterval(this.timer)
  50. }
  51. componentDidMount () {
  52. this.go()
  53. }
  54. render () {
  55. return (<div className="container" onMouseOver={this.stop} onMouseOut={this.go}>
  56. <SliderItems images={this.props.images} index={this.state.index} getUlRef={this.getUl} />
  57. <SliderArrow turn={this.turn} />
  58. <SliderDots images={this.props.images} index={this.state.index} turn={this.turn} />
  59. </div>)
  60. }
  61. }

/components/SliderArrow.js

  1. import React, { Component } from 'react'
  2. export default class SliderArrow extends Component {
  3. render () {
  4. return ( <div className="slider-arrow">
  5. <span className="left" onClick={() => this.props.turn(-1)}>&lt;</span>
  6. <span className="right" onClick={() => this.props.turn(1)}>&gt;</span>
  7. </div>)
  8. }
  9. }

/components/SliderDots.js

  1. import React, { Component } from 'react'
  2. export default class SliderDots extends Component {
  3. render () {
  4. // 轮播图下的焦点,需要根据当前轮播图正在展示的图片,给对应索引索引的小点增加选中样式,所以小点也需要 索引,此时 SliderDots 和 SliderItems 都需要 索引,所以应该使用把索引提升
  5. let { props: { images } } = this
  6. let cpImg = images.slice(0, 3)
  7. return (<div className="slider-dots">
  8. {
  9. images.map((item, index) => {
  10. if (index === 0 && this.props.index === images.length) {
  11. return <span className={'active'} key={index} onClick={(e) => this.props.turn(index - this.props.index)}></span>
  12. } else {
  13. return <span className={index === this.props.index ? 'active' : '' } key={index} onClick={(e) => this.props.turn(index - this.props.index)}></span>
  14. }
  15. })
  16. }
  17. </div>)
  18. }
  19. }

/components/SliderItem.js

  1. import React, { Component } from 'react'
  2. export default class SliderItems extends Component {
  3. constructor (props) {
  4. super()
  5. }
  6. render () {
  7. // 轮播图就是根据当前 container 要展示的 图片的索引,移动 wrapper,所以 wrapper 需要当前要展示图片的索引
  8. // 根据当前轮播图展示图片的索引动态计算 wrapper 的 left 值
  9. let style;
  10. let { props: { index } } = this
  11. style = {
  12. left: -1 * index * 500 + 'px',
  13. transition: 'all .5s linear 0s'
  14. }
  15. return (<ul className="wrapper" style={style} ref={this.props.getUlRef}>
  16. {
  17. this.props.images.map((item, index) => {
  18. return (<li className="slider" key={index}>
  19. <img src={item} alt=""/>
  20. </li>)
  21. })
  22. }
  23. <li className="slider">
  24. <img src={this.props.images[0]} alt=""/>
  25. </li>)
  26. </ul>)
  27. }
  28. }
  29. }