二、react面向组件编程(一)

2019-02-17  本文已影响0人  liquan_醴泉

摘要: 以前我们熟悉的jquery编程的方式是以面向对象的方式来进行编程的,而react将这种编程方式提升了一个层级,及面向对象---》 面向模块 ----》 面向组件。react面向组件编程无非两个步骤,定义组件和渲染组件。接下来将学习如何定义组件,以及渲染组件。

1. react组件的分类

2. 自定义组件

2.1) 工厂函数组件(简单组件)如

// 函数式组件必须要有个return
function MyComponent () {
 return <h2>函数式组件</h2>
}

以下用用函数式组件编写最简单的demo

<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title>react-demo1</title>
  </head>
  <body>
    <div id="root">

    </div>

    <!--react的核心库-->
    <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <!--是基于react的专门操作dom的扩展库-->
    <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

    <!-- Don't use this in production: -->
    <script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>

    <script type="text/babel">
      // 定义组件
      function MyComponent () {
        return <h2>react 学习</h2>
      }

      // 渲染组件
      ReactDOM.render(<MyComponent/>, document.getElementById('root'))
    </script>

  </body>
</html>

2.2) es6类组件(复杂组件)如

  // 这种方式继承了React的核心组件,并重写了render方法,类实例通过调用render方法来创建虚拟dom
    class MyComponent extends React.Component{
        render () {
          return <h2>学习react类组件</h2>
        }
      }

以下是基于类组件最简单的demo:

<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title>react-demo1</title>
  </head>
  <body>
    <div id="root">

    </div>

    <!--react的核心库-->
    <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <!--是基于react的专门操作dom的扩展库-->
    <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

    <!-- Don't use this in production: -->
    <script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>

    <script type="text/babel">
      // 定义组件
      class MyComponent extends React.Component {
        render () {
          return <h2>react 类组件的学习</h2>
        }
      }
      // 渲染组件
      ReactDOM.render(<MyComponent/>, document.getElementById('root'))
    </script>

  </body>
</html>

这里分析类了组件如何定义使用,到这里心中应该有个疑问,既然是类,那么他的人实例应该长什么样子?类组件重写了React的render方法,这个render方法一定是由组件类的实例所调用的,因此在render的方法中是可以访问到这个实例的,其实就i是常见的this,所以有必要在render中打印this一探究竟,如下:

 <script type="text/babel">
      // 定义组件
      class MyComponent extends React.Component {
        render () {
          console.log(this) // 查看组件实例对象
          return <h2>react 类组件的学习</h2>
        }
      }
      // 渲染组件
      ReactDOM.render(<MyComponent/>, document.getElementById('root'))
    </script>

通过打印this得到如下的结果:


image.png

可以看到有属性,有方法。接下来我们就来看看这些属性和方法的作用,(
红色框起来的是重点),究竟是用来干嘛的。

3. 组件三大属性

/**
* 首先要先知道以下几点:
* 1. 在构造器中进行初始化操作,react 类组件提供了构造器
* 2. 要更新状态要通过事件触发,那么如何绑定事件
* 3. 通过setState来更新状态的值,更新状态前可能需要读取状态
*
*****/
<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title>react-demo1</title>
  </head>
  <body>
    <div id="root">

    </div>
    <div id="box">

    </div>
    <!--react的核心库-->
    <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <!--是基于react的专门操作dom的扩展库-->
    <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

    <!-- Don't use this in production: -->
    <script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>

    <script type="text/babel">
  /**
      * 需求: 自定义组件,功能说明如下:
      *
      * 1. 显示h2标题,初始化为本为: 我对代码如初恋
      * 2. 点击标题更新为: 代码虐我千百遍
      */
    // 定义组件
    class MyLove extends React.Component {

      constructor (props) {
        super(props) // 这是固定写法,react库的常规套路

        // 初始化state
        this.state = {
          isChange: false
        }

        // 绑定this
        this.handleClick = this.handleClick.bind(this)
      }

      // 事件处理函数,函数内部的this并不是组件内部的this,要让this指向组件对象,可以在构造函数中绑定this
      // 通过 this.handleClick = this.handleClick.bind(this)的方式来实现
      handleClick () {
        // 更新状态,注意react中更新状态的唯一方式是通过setState方法,接受一个对象

        // 跟新状态前先获取一下(这里获取后直接取反了)
        const isChange = !this.state.isChange
        // 设置状态
        this.setState({isChange}) // 这是es6的简写, 相当于 this.setState({isChange: isChange})
      }
      render () {

        // 获取状态
        const {isChange} = this.state
        // 注意: onClick事件,很像原声的onclick,但是用了驼峰的写法,因为他并不是原生事件,而且后面不是引号括起来的字符串
        // 而是用{} 括起来的js代码,点击的时候执行函数,在函数里面进行状态的更新,而且onClick={this.handleClick}中的handleClick后面没有加括号,因为react是声明式的,我们只需要告诉他我们要做什么,具体怎么做我们不管,加括号了就相当于我们手动调用了
        return <h2 onClick={this.handleClick}>{isChange ? '代码虐我千百遍' : '我对代码如初恋'}</h2>
      }

    }

    // 渲染虚拟dom到真实dom中
    ReactDOM.render(<MyLove/>, document.getElementById('root'))
    </script>

  </body>
</html>

这个示例尽管简单,但是说明了几个问题,1.修改状态数据确实能改变视图,体现了react的数据驱动思想,2.设置更新状态唯一的方式是通过setState方法,3.通过this.state.属性可以直接获取状态。4.如何进行事件绑定以及this指向的问题的。

<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title>react-demo1</title>
  </head>
  <body>
    <div id="root">

    </div>
    <div id="box">

    </div>
    <!--react的核心库-->
    <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <!--是基于react的专门操作dom的扩展库-->
    <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

    <!-- Don't use this in production: -->
    <script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
  
    <script type="text/babel">
    /***
    * 需求,自定义一个显示年龄信息的组件
    * 要求显示姓名,年龄,性别
    * 从这个需求可以分析出,当前组件应该是没有状态的,他的数据应该都来自外部,因此创建组件可以通过类组件, 也可以通过函数组件
    ************/

    function Person (props) {
      return (
        <ul>
          <li>姓名:{props.name}</li>
          <li>年龄:{props.age}</li>
          <li>性别:{props.sex}</li>
        </ul>
      )
    }

    // 定义人员信息数据
    const person1 = {
      name: '张三',
      age: 20,
      sex: '男'
    }

    // 类似dom标签属性一样给Person组件传值
    ReactDOM.render(<Person name={person1.name} age={person1.age} sex={person1.sex}/>, document.getElementById('root'))
    </script>

  </body>
</html>


这个demo很明显的说明了props的作用,传递给组件Person的属性全部由props接收。但是上面的这个例子还有一些小缺陷,比如,如果age,sex没有传递给组件,呢么Person组件是拿不到age,和sex的。name能不能再age和sex没有被传递的情况下给一个默认值了?如果组件要求name必须传递,age需要传递数值类型的,该如何验证了?首先说明一下,组件是可以给默认值得,也可以做类型和唯一校验的,接下来就添加这些规则:

<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title>react-demo1</title>
  </head>
  <body>
    <div id="root">

    </div>
    <div id="box">

    </div>
    <!--react的核心库-->
    <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <!--是基于react的专门操作dom的扩展库-->
    <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

    <!-- Don't use this in production: -->
    <script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
    <script src="https://unpkg.com/prop-types@15.6/prop-types.js"></script>
    <script type="text/babel">
    /***
    * 需求,自定义一个显示年龄信息的组件
    * 姓名必须指定,如果性别没有指定默认为男
    * 如果年龄没有指定默认为18
    * 从这个需求可以分析出,当前组件应该是没有状态的,他的数据应该都来自外部,因此创建组件可以通过类组件, 也可以通过函数组件,当然也可通过类组件
    ************/
    // 定义组件
    function Person (props) {
      return (
        <ul>
          <li>姓名:{props.name}</li>
          <li>年龄:{props.age}</li>
          <li>性别:{props.sex}</li>
        </ul>
      )
    }

    // 添加默认值规则,通过给函数组件添加一个defaultProps属性来添加默认值
    Person.defaultProps = {
      age: 18,
      sex: '男'
    }

    // 添加类型校验和唯一性校验,通过一个库prop-types来校验,该库是react提供的,通过脚本的方式在上面引入
    // 现在用这种方式来指定
    Person.propTypes = {
      name: PropTypes.string.isRequired,
      age: PropTypes.number
    }

    // 定义人员信息数据
    const person1 = {
      name: '张三',
      age: 20,
      sex: '男'
    }

    const person2 = {
      name: '里斯',
      age: "20"
    }

    // 测试默认值,这里不传递sex和age
    // ReactDOM.render(<Person name={person1.name}/>, document.getElementById('root'))

    // 测试name属性唯一性和age的类型,
    // ReactDOM.render(<Person/>, document.getElementById('root'))//Warning: Failed prop type: The prop `name` is marked as required in `Person`, but its value is `undefined`.in Person

    ReactDOM.render(<Person name={person2.name} age={person2.age} sex="女"/>, document.getElementById('root')) // react.development.js:2721 Warning: Failed prop type: Invalid prop `age` of type `string` supplied to `Person`, expected `number`.


    </script>

  </body>
</html>

该demo虽然简单,但是从从如何传递props,如何做规则校验,如何给默认值都做了说明,可谓麻雀虽小。

<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title>react-demo1</title>
  </head>
  <body>
    <div id="root">

    </div>
    <div id="box">

    </div>
    <!--react的核心库-->
    <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <!--是基于react的专门操作dom的扩展库-->
    <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

    <!-- Don't use this in production: -->
    <script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
    <script src="https://unpkg.com/prop-types@15.6/prop-types.js"></script>
    <script type="text/babel">
    /***
    * 自定义组件,功能说明下:
    * 1. 点击按钮,提示第一个输入框中的值
    *
    * 2.当第二个输入框失去焦点的时候,提示这个输入框中的值
    *
    *
    *******/

    // 1. 定义组件
    class MyComponent extends React.Component{
      constructor (props) {
        super(props)

        this.showInput = this.showInput.bind(this)
        this.handleBlur = this.handleBlur.bind(this)
      }

      // 这个是通过refs来获取dom元素的
      showInput () {
        const input = this.refs.content // 得到dom元素, 可以通过refs获取到dom对象,进而操作dom

        // alert(input.value)

        // 因为通过ref回调的方式保存了input,所以可以直接获取
        alert(this.input.value)
      }
      // 所有的事件函数都可以接受一个event,这个是通过当前事件回调event来获取的
      handleBlur (event) {
        alert(event.target.value)
      }
      render () {

        /**
        //ref是个字符串
        <input type="text" ref="content"/> &nbsp;&nbsp;
        // ref可以是个回调函数,回调函数的参数就是当前dom元素,这里获取到dom元素input ,将其保存到组件自定义属性input中
        <input type="text" ref={input => this.input = input}/> &nbsp;&nbsp;

        ****/
        return (
          <div>

            <input type="text" ref="content"/> &nbsp;&nbsp;

            <input type="text" ref={input => this.input = input}/> &nbsp;&nbsp;

            <button onClick={this.showInput} >提示输入</button>&nbsp;&nbsp;
            <input type="text" placeholder="失去焦点提示内容" onBlur={this.handleBlur}/>
          </div>
        )
      }
    }

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

  </body>
</html>

这个小demo解释了refs属性的用法,通过ref标记dom元素,通过refs来获取dom元素,当然ref可以是字符串,也可以是回调函数,上面例子中的<input type="text" ref={input => this.input = input}/>   就是采用回调的方式,而且这种写法优雅,因为获取dom一次就被存起来了,不用每次都获取。

4. 组件组合使用(组件化)

组件化编程无非以下几个步骤:

  1. 拆分组件: 拆分界面,抽取组件
  2. 实现静态组件: 使用组件实现静态页面效果
  3. 实现动态组件: 动态显示初始化数据,交互功能(从绑定事件监听开始)
比如:实现如下图所示的todoList 案例demo: image.png

根据以上三个步骤进行编码工作 ,第一步其实就是图示的拆分方式,第二步编写三个静态组件,第三步做交互,改为动态组件。

// 编写静态组件
<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title>react-demo1</title>
  </head>
  <body>
    <div id="app">

    </div>
    <!--react的核心库-->
    <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <!--是基于react的专门操作dom的扩展库-->
    <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

    <!-- Don't use this in production: -->
    <script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
    <script src="https://unpkg.com/prop-types@15.6/prop-types.js"></script>
    <script type="text/babel">
    /**
    * 实现一个简单的todolist案例
    *
    **/

    // 1. 定义组件

    class App extends React.Component{
      render () {
        return (
          <div>
            <h1>todo list</h1>
            <Add/>
            <List/>
          </div>
        )
      }
    }

    // List组件
    class List extends React.Component{
      render () {
        return (
          <ul>
            <li>aaa</li>
            <li>bbb</li>
            <li>ccc</li>
            <li>ddd</li>
          </ul>
        )
      }
    }

    // Add组件
    class Add extends React.Component{
      render () {
        return (
          <div>
            <input type="text"/>
            <button>add</button>
          </div>
        )
      }
    }


    // 渲染组件
    ReactDOM.render(<App/>, document.getElementById('app'))
    </script>

  </body>
</html>



至此,静态组件已经编写完成了,接下来就是改成动态组件,首先需要明确几个问题,这里有三个组件APP,Add,List。那么我们应该吧数据状态放在那个组件中?到这里我们必须明确知道到底需要哪些数据,其实这里只要一个list列表数据就可以了,我们可以称其为todos,这些数据是哪些组件使用的?很明显Add组件用,因为他要往里面添加数据,List也需要,因为他要读取。因此最好的方式是放在App中,因为App是他们共同的父组件,而且todos需要被Add和List共享。由此可以总结为:看数据是某一个组件需要还是某些组件需要,如果是某一个组件需要则放在组件内部,如果是某些组件需要,则放在他们公共的父组件中。

<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title>react-demo1</title>
  </head>
  <body>
    <div id="app">

    </div>
    <!--react的核心库-->
    <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <!--是基于react的专门操作dom的扩展库-->
    <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

    <!-- Don't use this in production: -->
    <script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
    <script src="https://unpkg.com/prop-types@15.6/prop-types.js"></script>
    <script type="text/babel">
    /**
    * 实现一个简单的todolist案例
    *
    **/

    // 1. 定义组件

    class App extends React.Component{

      constructor (props) {
        super(props)

        // 初始化组件状态
        this.state = {
          todos: ['吃饭', '睡觉', '打代码']
        }

        this.addTodos = this.addTodos.bind(this)
      }

      // 更新todos的行为方法,但是有个问题,调用addTodos是在子组件Add中调用的,那么只能通过将addTodos传到子组件执行
      addTodos (todo) {
        // this.state.todos.unshift(todo) 不能这么更改,因为我们知道更改state的唯一方式是setState,只有调用setState才能更新状态

        const {todos} = this.state
        todos.unshift(todo)

        // 更新状态
        this.setState({todos})
      }
      render () {

        const {todos} = this.state
        return (
          <div>
            <h1>todo list</h1>
            <Add count={todos.length} addTodos={this.addTodos}/>
            <List todos={todos}/>
          </div>
        )
      }
    }

    // List组件
    class List extends React.Component{
      render () {
        return (
          <ul>
            {
              // this.props.todos 是一个数据数组,这里需要转为标签数组
              this.props.todos.map((todo, index) => <li key={index}>{todo}</li>)
            }
          </ul>
        )
      }
    }

    // Add组件
    class Add extends React.Component{


      constructor (props) {
        super(props)

        this.add = this.add.bind(this)

      }
      add () {
        // 需要在add 组件修改App组件的状态,即子组件中改变父组件的状态,这是相当困难的,子组件不能直接更改父组件状态,那么怎么改了?
        // 也就是说状态在哪个组件,更新状态的行为就在哪个组件,这里因为todos在App组件中,那么修改todos的方法也在App中,那么在Add中怎么修改了,

        // 1. 读取输入数据
        const todo = this.todoInput.value.trim()
        // 2. 检查合法性
        if (!todo) {
          return
        }
        // 3. 添加
        this.props.addTodos(todo)

        // 清除输入
        this.todoInput.value = '' // this.todoInput其实就是dom的引用
      }
      render () {
        return (
          <div>
            <input type="text" ref={input => this.todoInput = input}/>
            <button onClick={this.add}>add#{this.props.count + 1}</button>
          </div>
        )
      }
    }

    // List组件需要接受一个todos数据,通过这样的方式接受
    List.propTypes = {
      todos: PropTypes.array.isRequired // 表示必须传递一个数组
    }

    Add.propTypes = {
      count: PropTypes.number.isRequired,
      addTodos: PropTypes.func.isRequired
    }
    // 渲染组件
    ReactDOM.render(<App/>, document.getElementById('app'))
    </script>

  </body>
</html>

此demo初步体现了组件化编程的思想。

5. 收集表单数据

表单在开发中都会用到,在react中,将表单组件分为受控和非受控两种,受控组件就是指表单项输入数据能够自动收集成状态,非受控组件是指需要表单项的值时手动读取表单输入框中的数据,以下通过demo来具体说明以下:

<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title>react-demo1</title>
  </head>
  <body>
    <div id="app">

    </div>
    <!--react的核心库-->
    <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <!--是基于react的专门操作dom的扩展库-->
    <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

    <!-- Don't use this in production: -->
    <script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
    <script src="https://unpkg.com/prop-types@15.6/prop-types.js"></script>
    <script type="text/babel">
    /*
    * 受控组件: 自己能够收集表单数据,我们只需要读取状态即可,需要定义状态,需要添加onChange事件,写起来比较麻烦
    * 非受控组件: 我们通过手动收集表单输入框中的值
    */
    class LoginForm extends React.Component{

      constructor (props) {
        super(props)

        // 初始化状态
        this.state = {
          pwd: ''
        }
        this.handleSubmit = this.handleSubmit.bind(this)
        this.handleChange = this.handleChange.bind(this)
      }

      handleSubmit (event) {
        const username = this.nameInput.value

        const pwd = this.state.pwd
        console('用户名为:'+username + '密码为:' + pwd)
        event.preventDefault()

      }

      handleChange (event) {
        // 读取输入的值
        const pwd = event.target.value
        // 更新pwd的状态
        this.setState({pwd})
      }
      render () {
        // 该组件中表单项input既有受控的,也有非受控的,因此当前这个组件既是受控组件也是非受控组件
        return (
          <form onSubmit={this.handleSubmit}>
            用户名: <input type="text" ref={input => this.nameInput = input} />
            密码: <input type="password" value={this.state.pwd} onChange={this.handleChange}/>
            <input type="submit" value="登录"/>
          </form>
        )
      }
    }

    // 渲染dom
    ReactDOM.render(<LoginForm/>,document.getElementById('app'))
    </script>

  </body>
</html>

/**
* 这段代码中,受控项是密码输入框,在输入的时候,会自动收集输入框的值到state* 中,非受控项是用户名输入框,当需要输入框中的值的时候必须手动获取,如 const username = this.nameInput.value 就是在用到的时候才获取的
*
*
**/

以上就是对受控组件和非受控组件的阐述,从代码的简洁性上非受控组件简单,因为非受控组件不需要绑定onChange事件之类的。但是从react的思想出发,受控组件优于非受控组件,因为非受控组件其实就是在操作dom.

6.组件声明周期(组件从生到死的过程)

生命周期流程:
  (一).第一次初始化渲染

重要的几个钩子函数:

上一篇 下一篇

猜你喜欢

热点阅读