第二章 React面向组件编程

基本理解和使用

自定义组件

  • 定义组件
  • 工厂函数组件(简单组件)
function MyComponent () {
    return <h2>工厂函数组件(简单组件)</h2>
}

没有状态的组件

  • ES6类组件(复杂组件)
class MyComponent2 extends React.Component {
    render () {
        console.log(this) // MyComponent2的实例对象
        return <h2>ES6类组件(复杂组件)</h2>
    }
}
  • 渲染组件标签
ReactDOM.render(<MyComponent />, document.getElementById('example1'))

注意事项

组件名必须首字母大写

虚拟DOM元素只能有一个根元素

虚拟DOM元素必须有结束标签

 

组件三大属性(state)
  • state是组件对象最重要的属性, 值是对象(可以包含多个数据)
  • 组件被称为"状态机", 通过更新组件的state来更新对应的页面显示(重新渲染组件)

尚硅谷 python笔记 尚硅谷react笔记_尚硅谷

操作

  • 初始化状态
constructor (props) {
    super(props)
    // 初始化状态
    this.state = {
        isLikeMe: true
    }
  • 读取某个状态->绑定this为组件对象
this.change = this.change.bind(this)
  • 更新状态->组件界面更新
this.setState({
    isLikeMe: !this.state.isLikeMe
})

 

组件三大属性(props)
  • 每个组件对象都会有props(properties的简写)属性
  • 组件标签的所有属性都保存在props中

操作

  • 内部读取某个属性值
this.props.propertyName
  • 对props中的属性值进行类型限制和必要性限制
Person.propTypes = {
    name: PropTypes.string.isRequired,
    sex: PropTypes.string,
    age: PropTypes.number
}
  • 扩展属性: 将对象的所有属性通过props传递
<Person {...person}/>

... 的作用:

打包

function fn(...as) {} fn( 1,2,3)

解包

const arr1 =[1,2,3] const arr2 = [6, ...arr1, 9]

  • 默认属性值
Person.defaultProps = {
    sex: '男',
    age: 18
}
  • 组件类的构造函数
constructor (props) {
    super(props)
    console.log(props) // 查看所有属性
}

 

props和state属性区别

props

state

用于定义外部接口

用于记录内部状态

赋值在于外部世界使用组件

赋值在于组件内部

不改变组件值

让组件来修改的

不能在constructor中设置默认值

只能在constructor中设置默认值

从组件外部向组件内部传递数据, 组件内部只读不修改

组件自身内部可变化的数据

none

setState修改值为异步的

 

组件三大属性(refs与事件处理)

尚硅谷 python笔记 尚硅谷react笔记_尚硅谷_02

//定义组件
    class MyComponent extends React.Component {
        constructor(props) {
            super(props) // 调用父类(Component)的构造函数
            //console.log(this)
            // 将自定义的函数强制绑定为组件对象
            this.showInput = this.showInput.bind(this) // 将返回函数中的this强制绑定为指定的对象, 并没有改变原来的函数中的this
        }

        // 自定义的方法中的this默认为null
        showInput() {
            // alert(this) //this默认是null, 而不是组件对象
            // 得到绑定在当前组件对象上的input的值
            alert(this.msgInput.value)
        }

        handleBlur(event) {
            alert(event.target.value)
        }

        render() {
            return (
                <div>
                    <input type="text" ref={input => this.msgInput = input}/>{' '}
                    <button onClick={this.showInput}>提示输入数据</button>
                    {' '}
                    <input type="text" placeholder="失去焦点提示数据" onBlur={this.handleBlur}/>
                </div>
            )
        }
    }

    // 渲染组件标签
    ReactDOM.render(<MyComponent/>, document.getElementById('example'))

refs属性

  • 组件内的标签都可以定义ref属性来标识自己
  • <input type="text" ref={input => this.msgInput = input}/>
  • 回调函数在组件初始化渲染完或卸载时自动调用
  • 在组件中可以通过this.msgInput来得到对应的真实DOM元素
  • 作用: 通过ref获取组件内容特定标签对象, 进行读取其相关数据

事件处理

  • 通过onXxx属性指定组件的事件处理函数(注意大小写)
  • React使用的是自定义(合成)事件, 而不是使用的原生DOM事件
  • React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)
  • 通过event.target得到发生事件的DOM元素对象
<input onFocus={this.handleClick}/>
    handleFocus(event) {
    event.target  //返回input对象
}

注意

  • 组件内置的方法中的this为组件对象
  • 在组件类中自定义的方法中this为null
  • 强制绑定this: 通过函数对象的bind()
  • 箭头函数(ES6模块化编码时才能使用)

 

组件的组合

流程

  • 拆分组件: 拆分界面,抽取组件
  • 实现静态组件: 使用组件实现静态页面效果(只有静态界面,没有动态数据和交互)
  • 实现动态组件
  • 实现动态显示初始化数据
  • 交互功能(从绑定事件监听开始)

尚硅谷 python笔记 尚硅谷react笔记_react_03

问题1:数据保存在哪个组件内?
看数据是某个组件需要(给它),还是某些组件需要(给共同的父组件)
问题2:需要在子组件中改变父组件的状态
子组件中不能直接改变父组件的状态
状态在哪个组件,更新状态的行为就应该定义在哪个组件
解决:父组件定义函数,传递给子组件,子组件调用

<script type="text/babel">
    
    class App extends React.Component {
        constructor(props) {
            super(props)
            // 初始化状态
            this.state = {
                todos: ['吃饭', '睡觉', '打豆豆']
            }
            this.addTodo = this.addTodo.bind(this)
        }

        addTodo(todo) {
            const {todos} = this.state
            todos.unshift(todo)
            //更新状态
            this.setState({todos})
        }

        render() {
            const {todos} = this.state
            return (
                <div>
                    <h1>Simple TODO List</h1>
                    <Add count={todos.length} addTodo={this.addTodo}/>
                    <List todos={todos}/>
                </div>
            )
        }
    }

    class Add extends React.Component {
        constructor(props) {
            super(props);
            this.add = this.add.bind(this)
        }

        add() {
            // 读取输入数据
            const todo = this.todoinput.value.trim()
            // 检查合法性
            if (!todo) {
                return
            }
            // 保存到todos
            this.props.addTodo(todo)
            // 清除输入
            this.todoinput.value = ''
        }

        render() {
            return (
                <div>
                    <input type="text" ref={input => this.todoinput = input}/>
                    <button onClick={this.add}> add #{this.props.count + 1}</button>
                </div>
            )
        }
    }

    Add.propTypes = {
        count: PropTypes.number.isRequired,
        addTodo:PropTypes.func.isRequired
    }

    class List extends React.Component {
        render() {
            const {todos} = this.props
            return (
                <ul>
                    {
                        todos.map((todo, index) => {
                            return <li key={index}>{todo}</li>
                        })
                    }
                </ul>
            )
        }
    }

    List.propTypes = {
        todos: PropTypes.array.isRequired,
    }
    // 渲染应用组件标签
    ReactDOM.render(<App/>, document.getElementById('example'))

</script>

 

收集表单数据
  • 包含表单的组件分类
  • 受控组件: 表单项输入数据能自动收集成状态
  • 非受控组件: 需要时才手动读取表单输入框中的数据

效果

尚硅谷 python笔记 尚硅谷react笔记_尚硅谷 python笔记_04

<script type="text/babel">
    class LoginForm extends React.Component {
        constructor(props) {
            super(props)
            // 初始化状态
            this.state = {
                username: ''
            }
            this.handleSubmit = this.handleSubmit.bind(this)
            this.handleChange = this.handleChange.bind(this)
        }

        handleChange(event) {
            this.setState({username: event.target.value})
        }

        handleSubmit(event) {
            alert(`准备提交的用户名为: ${this.state.username}, 密码:${this.pwdInput.value}`)

            // 阻止事件的默认行为: 提交表单
            event.preventDefault()
        }

        render() {
            return (
                <form onSubmit={this.handleSubmit} action="/test">
                    <label>
                        用户名:
                        <input type="text" value={this.state.username} onChange={this.handleChange}/>
                    </label> 
                    <label>
                        密码:
                        <input type="password" ref={(input) => this.pwdInput = input}/>
                    </label> 
                    <input type="submit" value="登陆"/>
                </form>
            )
        }
    }

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

 

组件生命周期

生命周期表

效果

  • 组件对象从创建到死亡它会经历特定的生命周期阶段
  • React组件对象包含一系列的勾子函数(生命周期回调函数), 在生命周期特定时刻回调

尚硅谷 python笔记 尚硅谷react笔记_生命周期_05

<script type="text/babel">
  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 () {
        // 得到当前opacity
        let {opacity} = this.state
        // 更新opacity
        opacity -= 0.1
        if(opacity<=0) {
          opacity = 1
        }
        // 更新状态
        this.setState({opacity})
      }.bind(this), 200)
    }

    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>

生命周期流程图

  • 组件对象从创建到死亡它会经历特定的生命周期阶段
  • React 组件对象包含一系列的钩子函数(生命周期回调函数),在生命周期特定时刻回调
  • 我们在定义组件时,可以重写特定的生命周期回调函数,做特定的工作

尚硅谷 python笔记 尚硅谷react笔记_生命周期_06

生命周期详述

  • Mount:挂载过程,第一次将组件插入到真实 DOM
  • Update:更新过程,组件被重新渲染
  • Unmount:卸载过程,被移出真实 DOM

生命周期流程

尚硅谷 python笔记 尚硅谷react笔记_基础入门_07

1)创建阶段(第一次初始化渲染显示)

ReactDOM.render()

  • constructor():super(props) 指定 this,this.state={} 创建初始化状态(getDefaultProps、getInitialState)
  • componentWillMount()
    :组件将要挂载到页面上
  • 可以在这里调用 setState() 方法修改 state
  • render():创建虚拟 DOM 但是还没有挂载上去
  • componentDidMount()
    :已经挂载到页面上(初始界面已经渲染完毕)
  • 可以在这里通过 this.getDOMNode() 来进行访问 DOM 结构
  • 可以在这里发送 ajax 请求
  • 添加监听器/订阅

2)运行阶段(二次渲染)

父组件传递的 props 发生更新,就会调用 componentWillReceiveProps()

  • componentWillReceiveProps(nextProps):当子组件接受到 nextProps 时,不管这个 props 与原来的是否相同都会调用

props 改变或者调用 this.setState() 方法更新 state,都会触发组件的更新,调用后面的钩子函数

  • shouldComponentUpdata(nextProps, nextState):接收一个新的 props 和 state,返回true/false,表示是否允许更新
  • 通常情况下为了优化,需要对新的 props 以及 state 和原来的数据作对比,如果发生变化才更新

调用 this.forceUpdate() 方法会直接进入 componentWillUpdate。跳过 shouldComponentUpdate()

  • componentWillUpdate():将要更新
  • render():重新渲染
  • componentDidUpdate():已经完成更新

除了首次 render 之后调用 componentDidMount,其它 render 结束之后都是调用 componentDidUpdate

3)销毁阶段(移除组件)

执行 ReactDOM.unmountComponentAtNode(containerDom) 用来使组件从真实 DOM 中卸载(开始销毁阶段)

  • componentWillUnmount():组件将要被移除时(移出前)回调
  • 一般在 componentDidMount 里面注册的事件需要在这里删除

重要勾子

  • render():初始化渲染时或更新渲染时调用
  • componentDidMount():开启监听,可以初始化一些异步操作:启动定时器/发送 ajax 请求
  • componentWillUnmount():做一些收尾工作,如:清理定时器
  • componentWillReceiveProps():当组件接收到(父元素传递的)新的 props 属性前调用

 

虚拟DOM与DOM Diff算法

效果

尚硅谷 python笔记 尚硅谷react笔记_生命周期_08

<script type="text/babel">
  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"/>! 
          <span>It is {this.state.date.toTimeString()}</span>
        </p>
      )
    }
  }

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

基本原理图

尚硅谷 python笔记 尚硅谷react笔记_基础入门_09