模块与组件

  • 模块:向外提供特定功能的js程序, 一般就是一个js文件
  • 组件:用来实现特定(局部)功能效果的代码集合(html/css/js)

自定义组件

自定义组件的两种方式

工厂函数组件

  1. function MyComponent (props) {
  2. return <h2>工厂函数组件(简单组件)</h2>
  3. }
  4. // 渲染
  5. ReactDOM.render(<MyComponent1 />, document.getElementById('example1'))

ES6 类组件

  1. class MyComponent2 extends React.Component {
  2. render () {
  3. console.log(this) // MyComponent2的实例对象
  4. return <h2>ES6类组件(复杂组件)</h2>
  5. }
  6. }
  7. // 渲染
  8. ReactDOM.render(<MyComponent2 />, document.getElementById('example2'))

注意

:::warning

  • 组件名必须首字母大写
  • 虚拟 DOM 元素只能有一个根元素
  • 虚拟 DOM 元素必须有结束标签 :::

组件三大属性:state

  • state 是组件对象最重要的属性, 值是对象(可以包含多个数据)

组件被称为”状态机”, 通过更新组件的 state 来更新对应的页面显示(重新渲染组件) :::info state 属性只能在 ES6类 创建的组件中使用,工厂函数 创建的组件不能使用,没有 state 属性。 :::

state 初始化

  1. import React from 'react'
  2. // 第一种,使用 constructor 构造函数
  3. class CompoName extends React.Component {
  4. constructor(props) {
  5. super(props)
  6. this.state = {
  7. stateProp1: value1,
  8. stateProp2: value2
  9. }
  10. }
  11. render () {...}
  12. }
  13. // 第二种 ES6 class 的变式
  14. class CompoName extends React.Component {
  15. state = {
  16. stateProp1: value1,
  17. stateProp2: value2
  18. }
  19. render () {...}
  20. }

获取 state 的值

  1. this.state.statePropertyName

更新状态

  1. this.setState({
  2. stateProp1 : value1,
  3. stateProp2 : value2
  4. })

:::warning 注意:state 的值只能通过 setState 函数改变,这样才能刷新界面。 :::

组件三大属性:props

每个组件对象都会有props(properties的简写)属性。 :::info

  • 工厂函数创建的组件,props 通过函数参数传递
  • ES6类创建的组件,props 自动传入,使用构造函数时,一定要有 super(props) 代码。 :::

:::warning 组件内部不要修改props数据 :::

读取 props

  1. // ES6 class 方式
  2. this.props.propertyName

限制 props

需要引入 prop-types 库,可以参考npm

  1. npm install --save prop-types
  1. import PropTypes from 'prop-types'
  2. class Comp extends React.Component {
  3. // 第一种方式:使用static
  4. static propTypes = {
  5. name: PropTypes.string.isRequired,
  6. add: PropTypes.func.isRequired
  7. }
  8. }
  9. // 第二种方式:在 class 外
  10. Comp.propTypes = {
  11. name: PropTypes.string.isRequired,
  12. add: PropTypes.func.isRequired
  13. }

默认 props

  1. class App extends React.Component {
  2. // => 设置默认的props
  3. static defaultProps = {
  4. name: 'Mary'
  5. }
  6. render () {
  7. return (
  8. <div>{this.props.name}</div>
  9. )
  10. }
  11. }

扩展属性

使用 ...object ES6 语法。将对象的所有属性通过props传递

  1. <Person {...person}/>

组件三大属性:ref 和事件

ref

组件内的标签都可以定义ref属性来标识自己

  1. // 回调函数在组件初始化渲染完或卸载时自动调用
  2. <input type="text" ref={input => this.msgInput = input}/>
  3. // 在组件中可以通过 this.msgInput 来得到对应的真实DOM元素

例子

效果:
1.gif
代码:

import React from 'react'
class App extends React.Component {
  showOnClick = () => {
    alert(this.msgInput.value)
  }
  showOnBlue = (e) => {
    alert(e.target.value)
  }
  render () {
    return (
      <div>
        {/* ref 属性的使用 */}
        <input type='text'ref={input => this.msgInput = input}/> <button onClick={this.showOnClick}>显示</button> <br/>
        {/* 事件的使用 */}
        <input type='text' placeholder='失去焦点就显示' onBlur={this.showOnBlue}/>
      </div>
    )
  }
}
export default App

事件

通过onXxx属性指定组件的事件处理函数(注意大小写)。

  • 通过event.target得到发生事件的DOM元素对象,可参考上面的例子
// 这个简略,可参考上面的例子
<input onFocus={this.handleClick}/>
handleFocus(event) {
  event.target  //返回input对象
}

注意:方法中的 this 指向

:::warning

  • 从React.Component 继承来的方法(render, componentDidMount等)的 this 指向组件对象,不需要特殊处理。
  • 自定义的方法中 this 为 null,使 this 指向组件对象有两种方法:
    • 第一种方法:在 constructor 构造函数中使用 bind()
    • 第二种方法:使用箭头函数 :::

第一种方法示例:

class App extends Component {
  constructor(props) {
    super(props)
    this.methodName = this.methodName.bind(this)
  }
  methodName () {...}
}

第二种方法示例:

class App extends Component {
  methodName = () => {...}
}

收集表单数据

包含表单的组件分为:受控组件和非受控组件

  • 受控组件:表单项输入数据能自动收集成状态,一般与 state 的属性值绑定
  • 非受控组件: 需要时才手动读取表单输入框中的数据,一般使用 ref 属性

例子

效果:
2.gif
代码:

import React from 'react'
class App extends React.Component {
  state = {
    username: ''
  }
  handleChange = (e) => {
    const username = e.target.value
    this.setState({username})
  }
  handleSubmit = (e) => {
    e.preventDefault()
    alert(`用户名:${this.state.username},密码:${this.pwdInput.value}`)
  }
  render () {
    return (
      <div>
        <form onSubmit={this.handleSubmit} action="/test">
          <label>
            用户名:
            <input type="text" 
              value={this.state.username} 
              onChange={this.handleChange} placeholder='受控组件'/>
          </label>&nbsp;
          <label>
            密码:
            <input type="password" 
              ref={(input) => this.pwdInput = input} placeholder='非受控组件'/>
          </label>&nbsp;
          <input type="submit" value="登陆" />
        </form>
      </div>
    )
  }
}
export default App

组件的生命周期

3.png

例子

效果:
6.gif
代码:

<!DOCTYPE html>
<html>
<head></head>
<body>
<div id="example"></div>
<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
  /*
  需求: 自定义组件
    1. 让指定的文本做显示/隐藏的动画
    2. 切换时间为2S
    3. 点击按钮从界面中移除组件界面
   */
  class Fade extends React.Component {

    constructor (props) {
      super(props)
      console.log('constructor(): 创建组件对象')
      this.state = {
        opacity: 1
      }
      this.removeComponent = this.removeComponent.bind(this)
    }

    componentWillMount () {
      console.log('componentWillMount(): 初始化将要挂载')
    }

    componentDidMount () {// 在此方法中启动定时器/绑定监听/发送ajax请求
      console.log('componentDidMount(): 初始化已经挂载')
      // 保存到当前组件对象中
      this.intervalId = setInterval(function () {
        console.log('--------')
        // 得到当前opacity
        let {opacity} = this.state
        // 更新opacity
        opacity -= 0.1
        if(opacity<=0) {
          opacity = 1
        }
        // 更新状态
        this.setState({opacity})
      }.bind(this), 200) // <= 注意使用 bind(this), 否则要使用箭头函数
    }

    componentWillUpdate () {
      console.log('componentWillUpdate(): 将要更新')
    }
    componentDidUpdate () {
      console.log('componentDidUpdate(): 已经更新')
    }

    componentWillUnmount () {// 清除定时器/解除监听
      console.log('componentWillUnmount(): 将要被移除')
      clearInterval(this.intervalId)
    }

    removeComponent () {
      ReactDOM.unmountComponentAtNode(document.getElementById('example'))
    }

    render() {
      console.log('render() 渲染组件')
      return (
        <div>
          <h2 style={{opacity:this.state.opacity}}>{this.props.content}</h2>
          <button onClick={this.removeComponent}>不活了</button>
        </div>
      )
    }
  }
  ReactDOM.render(<Fade content="react学不会, 怎么办?"/>, document.getElementById('example'))
</script>
</body>
</html>

虚拟 DOM 与 DOM Diff 算法

基本原理图

5.png

例子

效果:
4.gif
代码:

class HelloWorld extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
        date: new Date()
    }
  }

  componentDidMount () {
    setInterval(() => {
      this.setState({
          date: new Date()
      })
    }, 1000)
  }

  render () {
    console.log('render()')
    return (
      <p>
        Hello, <input type="text" placeholder="Your name here"/>!&nbsp;
        It is {this.state.date.toTimeString()}
      </p>
    )
  }
}

ReactDOM.render(
  <HelloWorld/>,
  document.getElementById('example')
)