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标签有两种使用方式
- 一种是
img
自闭合标签<img />
- 一种是
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'));
这时有两点需要注意:
- 所有的组件名必须大写,以和普通的
html
标签进行区分
- 所有的组件都返回一个单一的根节点,这也是上面的
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定义组件时提供的一系列处理函数(钓子函数),这些函数会在组件生命周期的某个阶段调用
在组件挂载前(即:创建期)调用一次,其返回值将做为this.state的初始值
getInitialState()
方法会组件类创建的时候调用一次,其返回值会被缓存下来。该方法用于设置props属性的默认值,但仅对于非必须属性。如果父组件没有指定props中的某个值,此返回对象中的相应属性将会合并到this.props
getInitialState()
方法会在组件实例创建前调用,这时还不能使用this.props
属性,且其返回对象是在所有实例间共享的
componentWillMount()
服务器端和客户端都只调用一次,在初始化渲染执行之前被调用。如果在这个方法内调用setState()
方法,render()
方法将会收到更新后的state,也就是说这是我做在组件渲染前最后一个修改state的机会
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(object nextProps, object nextState)
componentWillUpdate()
会在收到新的props或state后调用,类似componentWillMount()
componentDidUpdate(object prevProps, object prevState)
componentDidUpdate()
会在组件重新渲染后立即被调用,当我们需要在组件重新渲染后操作DOM则需要使用这个方法
销毁&清理期 |
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文件,但是同时导入了PublicA和PublicB组件了
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;