Skip to content

React组件 #10

@Wscats

Description

@Wscats

React组件

React提供了两种方式来声明组件,一种是函数式,一种是类式,就是用ES6的class我们所有的组件都继承自React.Component
函数式很简单,就像我们平常写函数一个,接受一个参数作为输入,然后进行相应的输出,只不过它输出的jsx格式

函数式组件

注意组件只能有一个根元素

function Wscats(props) {  
    return  <h1> {props.name}</h1>  
}

//ES6
const Wscats = ({props}) => (
  <div>
    <h1>{props.name}</h1>
  </div>
)

类式组件

import React from 'react';
//推荐这种
class Wscats extends React.Component {
  render() {
    return  <h1> {this.props.name}</h1>
  }
}
//or 这种方法将要废弃
var Wscats = React.createClass({
  render() {
    return  <h1> {this.props.name}</h1>
  }
}

渲染组件

HTML标签怎么使用,组件就怎么使用

HTML标签有两种使用方式

  1. 一种是img自闭合标签<img />
  2. 一种是h1之类的双标签<h1></h1>

同理,组件也有这两种使用方式。传参则像是给html标签写属性,属性名 = 属性值,如name =”Wscats” , 组件内部的props,则把这些属性组合在一起形成对象{name: "Oaoafly"}

<Wscats name="Oaoafly" />//标签一定要闭合,就是后面的/不能漏掉
<Wscats></Wscats>

使用组件,它返回了<h1>Oaoafly</h1>,很像html代码,其实它是React所说的虚拟DOM,并不是真实的DOM,我们要把虚拟DOM渲染成真实的DOM,才能显示到页面中,这需要用到ReactDOM的render方法,它接受的第一个参数,就是虚拟DOM,第二个参数就是我们要把DOM渲染到什么地方

ReactDOM.render(
    {/*虚拟DOM*/}
    <Wscats name="Oaoafly" />,
    {/*DOM渲染的地方*/}
    document.getElementById('root')
);

render()应该是一个纯粹函数,即使不使用组件的state,每次调用都返回同样的结果,不读写DOM,不与浏览器交互,只做最单纯的渲染。当需要修改状态或有交互时,应该在componentDidMount等组件生命周期函数中进行

Props传值

属性值是一个字符串,是最简单的方式进行传值,其实属性值可以是任何的JS表达式,只要把它们包括在{}中,在jsx中,{}里面的所有东西都当作JS表达式进行解析,比如我们向组件中传递一个数字18,我们就可以写age = {18}

function Wscats(props) {
  // 新增num 属性
  return <h1>
    {props.name}
    {props.age}</h1>
}

ReactDOM.render(
  <Wscats name="Oaoafly" age={16}/>, //JS表达式进行传值
    document.getElementById('root'));

如果要传递多个属性, 一个一个传会很麻烦,这时可以使用对象,但如果用对象进行传值,又不符合属性名=属性值的写法,这时要用到ES6中的扩展运算符...,React对ES6中的扩展运算符(…)进行扩展,它能运用到对象上,对对象进行分割,{…obj}

var obj = {
    name: "Wscats",
    age: 16
  }
{…obj} => name = "Wscats", age = 16,

正好对应父组件向子组件传递数据

var obj = { name: "Wscats", age : 16 } //函数式组件
function Wscats(props) { 
return (
    <div>
      <h1>{props.name}</h1>
      <h1>{props.age}</h1>
    </div>
)} // 渲染成真实的DOM
ReactDOM.render(
    <Wscats {...obj} />, document.getElementById('root') );

组件Props传对象

类式组件和函数组件可以互相组合

//类式组件
class Wscats extends React.Component {
  render() {
    return <h1>
      {this.props.name}</h1>
  }
}
//函数组件的组合
function App() {
  return (
    <div>
      <Wscats name="Oaoafly"/>
      <Wscats name="Eno"/>
    </div>
  );
}
ReactDOM.render(
  <App/>, //自闭合标签
    document.getElementById('root'));

这时有两点需要注意:

  1. 所有的组件名必须大写,以和普通的html标签进行区分
  2. 所有的组件都返回一个单一的根节点,这也是上面的render方式中把所有元素都放到一个div元素中的原因

类式组件和函数组件组合

组件状态

使用class声明组件有一个好处,就是组件内部可以有自己的状态,给组件添加一个内部状态,用的是类的构造函数,因为构造函数保存的就是实例身上的属性,可以看成这个实例(组件)的状态, 我们组件也可以使用这个状态

在这里一定要注意调用构造函数时,super()这是ES6的语法规定,子类中没有this, 只能调用super生成子类的 this,如果在调用super之前使用this,就会报错。 这个地方其实是不会变化的,可以看成一个模式。每次给组件添加状态的时候,我们就按照这个模式书写就可以了

//类式组件
class Wscats extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      date: new Date()
    } // 给组件添加状态
  }
  render() {
    return (
      <div>
        <h1>
          {this.props.name}</h1>
        <h2>现在时间是:{this.state.date.toLocaleTimeString()}</h2>
      </div>
    )
  }
}
//组件的组合
function App() {
  return (
    <div>
      <Wscats name="Oaoafly"/>
      <Wscats name="Eno"/>
    </div>
  );
}
ReactDOM.render(
  <App/>, //自闭合标签
    document.getElementById('root'));

所以往后在组件中添加状态,只要往this.state的对象中添加键值对就可以了

constructor(props){
    super(props);
    this.state = {name: "Hello Wscats"}  // 给组件添加状态
}

然后你就可以用表达式把state的状态呈现

<div>现在时间是:{this.state.date.toLocaleTimeString()}</div>

组件添加状态

注释

在render方法中添加注释,要用{/* */}方式

{/*要添加的注释*/}

生命周期

组件有状态,这涉及到组件的生命周期,react定义了非常完善的生命周期函数,组件渲染到页面中叫挂载(mounting),所以渲染完成后,叫做componentDidMount, 组件的卸载叫Unmount,所以组件将要卸载 叫做componentWillUnmount。我们想要在什么时候使用状态,就可以直接调用生命周期函数,把想要做的事情写到函数里面,生命周期函数直接写在组件内部

详情见 React组件生命周期

比如,页面渲染完成后时间自动加一秒,这时还要涉及到组件的状态更改。React不允许直接更改状态, 或者说,我们不能给状态(如: date)进行赋值操作, 必须调用组件的setState()方法去更改状态。这里写一个函数changeTime来更改状态

setState更改状态

changeTime函数也可以直接写到组件里面,根据ES6class语法的规定,直接写在类中的函数都会绑定在原型上,所以this.changeTime可以调用。但要保证this指向的是我们这个组件,而不是其他的东西, 这也是在setInterval中使用箭头函数的原因

//类式组件
class Wscats extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      date: new Date()
    } // 给组件添加状态
  }
  changeTime() {
    this.setState({date: new Date()})
  }
  // 生命周期函数
  componentDidMount() {
    setInterval(() => {
      this.changeTime()
    }, 1000)
  }
  render() {
    return (
      <div>
        <h1>
          {this.props.name}</h1>
        <h2>现在时间是:{this.state.date.toLocaleTimeString()}</h2>
      </div>
    )
  }
}
//组件的组合
function App() {
  return (
    <div>
      <Wscats name="Oaoafly"/>
      <Wscats name="Eno"/>
    </div>
  );
}

注意这里this的指向,如果不是箭头函数,this指向就是window

setInterval(function() {
  this.changeTime()
}.bind(this), 1000)
//or
setInterval(() => {
  this.changeTime()
}, 1000)

React中给元素添加事件,就像我们给元素添加行内事件一样简单,但这里也有不同的地方,事件名用驼峰命名法onClick,事件处理函数是函数名,用{}括起来

handleClick(e) {
    e.preventDefault();
    console.log(this);// null
    console.log("clicked");
}

在页面中点击可以看到handleClick中的事件处理函数console.log输出是null, 然后报错了。这主要是由于this是在函数运行的时候动态绑定的,this.handleClick指向了handleClick函数,点击的时候,这个函数开始执行,但this却没有指定,它是在哪个环境下执行, 由于ES6class是在严格模式下进行的,所以输出了null

我们必须使this指向我们这个组件,改变this指向可以使用两种方法,一种是ES5提供的bind()方法,它的第一个参数就是指定函数中this的,且它返回 一个函数,可以知道,返回的这个函数中this已经写死了,在程序运行的时候也不会变化了

this.handleClick = this.handleClick.bind(this)

解决方案1

也就是在构造函数中加上,此时调用this就可以查看修改到state的状态

constructor(props) {
  super(props);
  this.state = {
    date: new Date()
  } // 给组件添加状态
  this.handleClick = this.handleClick.bind(this)
}

解决方案2

这样也可以

<button onClick={this.handleClick.bind(this)}>点击</button>

解决方案3

<button onClick={() => this.handleClick()}>点击</button>

组件的this指向

生命周期相关参数,是React定义组件时提供的一系列处理函数(钓子函数),这些函数会在组件生命周期的某个阶段调用

创建期 getDefaultProps
object getDefaultProps()
创建期 getInitialState
object getInitialState()

在组件挂载前(即:创建期)调用一次,其返回值将做为this.state的初始值

getInitialState()方法会组件类创建的时候调用一次,其返回值会被缓存下来。该方法用于设置props属性的默认值,但仅对于非必须属性。如果父组件没有指定props中的某个值,此返回对象中的相应属性将会合并到this.props

getInitialState()方法会在组件实例创建前调用,这时还不能使用this.props属性,且其返回对象是在所有实例间共享的

创建期 componentWillMount
componentWillMount()

componentWillMount()服务器端和客户端都只调用一次,在初始化渲染执行之前被调用。如果在这个方法内调用setState()方法,render()方法将会收到更新后的state,也就是说这是我做在组件渲染前最后一个修改state的机会

创建期 componentDidMount
componentDidMount()

componentDidMount()会在组件初始化(渲染完成)后立即调用一次,我们一般在这个方法中使用this.getDOMNode()方法访问原始DOM

存在期 componentWillReceiveProps
componentWillReceiveProps(object nextProps)

componentWillReceiveProps()方法会在组件生命周期的存在期调用,当组件感知到props属性改变会,会调用此方法。render()方法将会在其后调用,这时我们可以通过this.setState()来阻止组件的再次渲染。

存在期 shouldComponentUpdate
boolean shouldComponentUpdate(object nextProps, object nextState)

shouldComponentUpdate()方法发生在组件生命周期的存在器,在组件收到新的props或state。在这个方法中,我们可以访问组件的props和state属性,通过这两个属性可以确认组件是否需要更新,如果不需要更新,则返回false,则其后的方法将不会在执行。如:

shouldComponentUpdate: function(nextProps, nextState) {
  return nextProps.id !== this.props.id;
}
存在期 componentWillUpdate
componentWillUpdate(object nextProps, object nextState)

componentWillUpdate()会在收到新的props或state后调用,类似componentWillMount()

存在期 componentDidUpdate
componentDidUpdate(object prevProps, object prevState)

componentDidUpdate()会在组件重新渲染后立即被调用,当我们需要在组件重新渲染后操作DOM则需要使用这个方法

销毁&清理期 componentWillUnmount
componentWillUnmount()

componentWillUnmount()是组件销毁&清理期唯一调用的方法,它会在组件从DOM中移除时被调用,这时我们可以清理一些状态或清理在componentDidMount中创建的DOM元素

样式

可以配置less样式,然后导入到组件,然后利用less的命名空间来约束组件的样式

import React, { Component } from 'react';
import {
	BrowserRouter as Router,
	Route,
	Link
} from 'react-router-dom';
import { matchRoutes, renderRoutes } from 'react-router-config';
import "./root.less";
//新版本的写法 推荐
//class Wscats extends React.Component {
class Wscats extends Component {
	render() {
		return( 
			<div id="root">
				<h1>Root</h1>
					<Link to="/">home</Link> <Link to="/index">index</Link>
					{/* child routes won't render without this */}
					{renderRoutes(this.props.route.routes)}
					{console.log(this)}
			</div>
		)
	}
}
module.exports = Wscats;

root.less

#root {
	h1 {
		font-size: 50px;
		color: green;
	}
}

导出多个组件

平时我们习惯一个单JSX文件导出一个组件,事实上,我们可以一个JSX文件导出多个组件,例如我们可以把一类相似功能的组件放在一起,然后类声明多个组件,然后再导出,我们就可以在使用的时候,导入一个组件,从而使用多个组件的功能

import React from "react";
// 第一个公有组件
class PublicA extends React.Component {
  constructor(props) {
    super(props)
    this.state = {}
  }
  render() {
    return (
      <div>
        第一个公有组件
      </div>
    )
  }
}

// 第二个公有组件
class PublicB extends React.Component {
  constructor(props) {
    super(props)
    this.state = {}
  }
  render() {
    return (
      <div>
        第二个公有组件
      </div>
    )
  }
}
// 默认导出一个,仅且一个
// export default PublicA
// 导出多个
export {PublicA, PublicB}

注意这里我们用的是export{xxx}而不是export default xxx

下面我们使用的时候就可以引入一个JSX文件,但是同时导入了PublicAPublicB组件了

import React from 'react';
// 导入多个组件
import {PublicA, PublicB} from "../components/public.jsx";
class PageB extends React.Component {
  constructor(props) {
    super(props);
    // 设置 initial state
    this.state = {};

  }
  componentDidMount() {}

  render() {
    return (
      <div>
        <PublicA/>
        <PublicB/>
      </div>
    )
  }
}
export default PageB;

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions