一、组件定义的方式
组件定义有两种方式:
- es5原生方式React.createClass定义的组件
- es6形式的extends React.Component定义的组件
我们只关注第二种方式
例子:
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
class Header extends Component {
render () {
return (
<div>
<h1>Hello React </h1>
</div>
)
}
}
ReactDOM.render(
<Header />,
document.getElementById('container')
)
二、render方法(render单词的含义为“着色”之意)
一个组件类必须要实现一个 render 方法,这个 render 方法必须要返回一个 JSX 元素。
2.1、必须要用一个外层的 JSX 元素把所有内容包裹起来。
即:
...
render () {
return (
<div>div1</div>
<div>div2</div>
)
}
...
是不合法的。
2.2、表达式插入
在 JSX 当中你可以插入 JavaScript 的表达式,表达式返回的结果会相应地渲染到页面上。表达式用 {} 包裹
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
const name = "world~~~~";
class Header extends Component {
render () {
return (
<div>
<h1>hello {name}</h1>
</div>
)
}
}
ReactDOM.render(
<Header />,
document.getElementById('container')
)
也可以是个函数返回值:
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
function hello() {
return "hello my world!";
}
class Header extends Component {
render () {
return (
<div>
<h1>{hello()}</h1>
</div>
)
}
}
ReactDOM.render(
<Header />,
document.getElementById('container')
)
简而言之,{} 内可以放任何 JavaScript 的代码,包括变量、表达式计算、函数执行等等。 render 会把这些代码返回的内容如实地渲染到页面上,非常的灵活。
表达式插入不仅仅可以用在标签内部,也可以用在标签的属性上
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
let className ="div1";
class Header extends Component {
render () {
return (
<div className={className}>
<h1>{hello()}</h1>
</div>
)
}
}
ReactDOM.render(
<Header />,
document.getElementById('container')
)
2.3、JSX 元素变量
JSX 元素其实可以像 JavaScript 对象那样自由地赋值给变量,或者作为函数参数传递、或者作为函数的返回值。
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
let div = <div>我的div~~</div>
class Header extends Component {
render () {
return (
<div className={className}>
<h1>{div}</h1>
</div>
)
}
}
ReactDOM.render(
<Header />,
document.getElementById('container')
)
三、组件
3.1、自定义组件嵌套
主文件
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
import MyHeader from "./Header";
import MyBody from "./Body";
import MyFooter from "./Footer";
class Index extends Component {
render () {
return (
<div>
<MyHeader/>
<MyBody/>
<MyFooter/>
</div>
)
}
}
ReactDOM.render(
<Index />,
document.getElementById('container')
)
引用组件
Mybody
import React, {Component} from 'react';
export default class MyBody extends Component {
render() {
return (<div><h2>我是BODY!!!</h2></div>)
}
}
MyFooter
import React, {Component} from 'react';
export default class MyFooter extends Component {
render() {
return (<div><h3>我是Footer!!!</h3></div>)
}
}
MyHeader
import React, {Component} from 'react';
export default class MyHeader extends Component {
render() {
return (<div><h1>我是HEADER!!!</h1></div>)
}
}
3.2、事件
3.2.1、监听事件
React.js 帮我们封装好了一系列的 on* 的属性,当你需要为某个元素监听某个事件的时候,只需要简单地给它加上 on* 就可以了。而且你不需要考虑不同浏览器兼容性的问题,React.js 都帮我们封装好这些细节了。
没有经过特殊处理的话,这些 on* 的事件监听只能用在普通的 HTML 的标签上,而不能用在组件标签上。也就是说,< Header onClick={…} /> 这样的写法不会有什么效果的。
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
function functionClick() {
alert("click event");
}
class Index extends Component {
render () {
return (
<div>
<p onClick={functionClick}>~~~~~</p>
</div>
)
}
}
ReactDOM.render(
<Index />,
document.getElementById('container')
)
3.2.2、获取event对象
和普通浏览器一样,事件监听函数会被自动传入一个 event 对象,这个对象和普通的浏览器 event 对象所包含的方法和属性都基本一致。不同的是 React.js 中的 event 对象并不是浏览器提供的,而是它自己内部所构建的。React.js 将浏览器原生的 event 对象封装了一下,对外提供统一的 API 和属性,这样你就不用考虑不同浏览器的兼容性问题。这个 event 对象是符合 W3C 标准( W3C UI Events )的,它具有类似于event.stopPropagation、event.preventDefault 这种常用的方法。
import React, {Component} from 'react'
import ReactDOM from 'react-dom'
function functionClick(e) {
alert(e.target.innerHTML);
}
class Index extends Component {
render() {
return (
<div>
<p onClick={functionClick}>~~~~~</p>
</div>
)
}
}
ReactDOM.render(
<Index/>,
document.getElementById('container')
)
3.2.3、事件中的this
React.js 调用你所传给它的方法的时候,并不是通过对象方法的方式调用(this.functionClick),而是直接通过函数调用 (functionClick),所以事件监听函数内并不能通过 this 获取到实例。
如果你想在事件函数当中使用当前的实例,你需要手动地将实例方法 bind 到当前实例上再传入给 React.js。
import React, {Component} from 'react'
import ReactDOM from 'react-dom'
class Index extends Component {
functionClick(e) {
console.log(this);
}
render() {
return (
<div>
<p onClick={this.functionClick.bind(this)}>~~~~~</p>
</div>
)
}
}
ReactDOM.render(
<Index/>,
document.getElementById('container')
)
3.3.4、组件的状态控制: state 和 setState
3.3.4.1、state
一个组件的显示形态是可以由它数据状态和配置参数决定的。一个组件可以拥有自己的状态,就像一个点赞按钮,可以有“已点赞”和“未点赞”状态,并且可以在这两种状态之间进行切换。React.js 的 state 就是用来存储这种可变化的状态的。
import React, {Component} from 'react'
import ReactDOM from 'react-dom'
class Index extends Component {
constructor() {
super();
this.state = {isLiked: false};
}
changeState() {
this.setState({
isLiked: !this.state.isLiked
})
}
render() {
return (
<div>
<button onClick={this.changeState.bind(this)}>{this.state.isLiked ? '取消' : '点赞'}</button>
</div>
)
}
}
ReactDOM.render(
<Index/>,
document.getElementById('container')
)
isLiked 存放在实例的 state 对象当中,这个对象在构造函数里面初始化。这个组件的 render 函数内,会根据组件的 state 的中的isLiked不同显示“取消”或“点赞”内容。并且给 button 加上了点击的事件监听。
3.3.4.2、setState
setState 接受对象参数
在 changeState 事件监听函数里面,大家可以留意到,我们调用了 setState 函数,每次点击都会更新 isLiked 属性为 !isLiked,这样就可以做到点赞和取消功能。
setState 方法由父类 Component 所提供。当我们调用这个函数的时候,React.js 会更新组件的状态 state ,并且重新调用 render 方法,然后再把 render 方法所渲染的最新的内容显示到页面上。
注意,当我们要改变组件的状态的时候,不能直接用 this.state = xxx 这种方式来修改,如果这样做 React.js 就没办法知道你修改了组件的状态,它也就没有办法更新页面。所以,一定要使用 React.js 提供的 setState 方法,它接受一个对象或者函数作为参数。
setState 回调函数
这里还有要注意的是,当你调用 setState 的时候,React.js 并不会马上修改 state。而是把这个对象放到一个更新队列里面,稍后才会从队列当中把新的状态提取出来合并到 state 当中,然后再触发组件更新。
修改我们上面的changeState方法:
changeState() {
console.log(this.state.isLiked)
this.setState({
isLiked: !this.state.isLiked
})
console.log(this.state.isLiked)
}
在函数执行时,会出现连续打印两个true或者两个false,而不是向我们预想的那样true、false交替打印。
我们可以使用回调函数,来解决
import React, {Component} from 'react'
import ReactDOM from 'react-dom'
class Index extends Component {
render() {
return (
<div>
<button onClick={this.changeState.bind(this)}>{this.state.isLiked ? '取消' : '点赞'}</button>
</div>
)
}
constructor() {
super();
this.state = {isLiked: false};
}
changeState() {
console.log(this.state.isLiked)
this.setState({
isLiked: !this.state.isLiked
}, () => {
console.log("callback")
console.log(this.state.isLiked)
})
console.log(this.state.isLiked)
}
}
ReactDOM.render(
<Index/>,
document.getElementById('container')
)
setState 接受函数参数(获取上一个state值)
当参数是函数的时候, setState() 会将上一个 setState() 的结果作为参数传入这个函数
import React, {Component} from 'react'
import ReactDOM from 'react-dom'
class Index extends Component {
render() {
return (
<div>
<button onClick={this.changeState.bind(this)}>{this.state.isLiked ? '取消' : '点赞'}</button>
</div>
)
}
constructor() {
super();
this.state = {isLiked: false};
}
changeState() {
this.setState((preState)=>{
console.log("上一个状态是"+preState.isLiked)
return {isLiked: !preState.isLiked}
},()=>{
console.log("回调函数调用,当前状态是"+this.state.isLiked)
})
}
}
ReactDOM.render(
<Index/>,
document.getElementById('container')
)
3.3.5、组件的显示控制:props
React中的数据流是单向的,只会从父组件传递到子组件。属性props(properties)是父子组件间进行状态传递的接口,React会向下遍历整个组件树,并重新渲染使用这个属性的组件。
import React, {Component} from 'react'
import ReactDOM from 'react-dom'
class Child extends Component {
render() {
return <div style={{backgroundColor: this.props.divColor}}>i'm child~~</div>;
}
}
class Parent extends Component {
constructor() {
super();
}
render() {
return (
<div>
<Child divColor ='red'/>
</div>
)
}
}
ReactDOM.render(
<Parent/>,
document.getElementById('container')
)
在使用props的时候,我们在组件外像在用普通的 HTML 标签的属性一样,可以把参数放在表示组件的标签上,组件内部就可以通过 this.props 来访问到这些配置参数了。
例如:
<Child divColor ='red'/>
组件内,props不能更改,只能访问,组件内部是通过 this.props 的方式获取到组件的参数。
默认props
import React, {Component} from 'react'
import ReactDOM from 'react-dom'
class Child extends Component {
static defaultProps = {
divColor: 'blue'
}
render() {
return (
<div style={{backgroundColor: this.props.divColor}}>i'm child~~</div>
)
}
}
class Parent extends Component {
constructor() {
super();
}
render() {
return (
<div>
<Child/>
</div>
)
}
}
ReactDOM.render(
<Parent/>,
document.getElementById('container')
)
我们可以使用 static defaultProps 给props一个默认的值:
static defaultProps = {
divColor: 'blue'
}
React.js 希望一个组件在输入确定的 props 的时候,能够输出确定的 UI 显示形态。如果 props 渲染过程中可以被修改,那么就会导致这个组件显示形态和行为变得不可预测,这样会可能会给组件使用者带来困惑。
但这并不意味着由 props 决定的显示形态不能被修改。组件的使用者可以主动地通过重新渲染(setState)的方式把新的 props 传入组件当中,这样这个组件中由 props 决定的显示形态也会得到相应的改变。
3.3.6、state和props区别
state 的主要作用是用于组件保存、控制、修改自己的可变状态。state 在组件内部初始化,可以被组件自身修改,而外部不能访问也不能修改。你可以认为 state 是一个局部的、只能被组件自身控制的数据源。state 中状态可以通过 this.setState 方法进行更新,setState 会导致组件的重新渲染。
props 的主要作用是让使用该组件的父组件可以传入参数来配置该组件。它是外部传进来的配置参数,组件内部无法控制也无法修改。除非外部组件主动传入新的 props,否则组件的 props 永远保持不变。
state 和 props 有着千丝万缕的关系。它们都可以决定组件的行为和显示形态。一个组件的 state 中的数据可以通过 props 传给子组件,一个组件可以使用外部传入的 props 来初始化自己的 state。但是它们的职责其实非常明晰分明:state 是让组件控制自己的状态,props 是让外部对组件自己进行配置。
如果你觉得还是搞不清 state 和 props 的使用场景,那么请记住一个简单的规则:尽量少地用 state,尽量多地用 props。
3.3.6、共享状态提升
使用 react 经常会遇到几个组件需要共用状态数据的情况。比如我们想要通过一个子组件来改变一个父容器组件中另一个子组件的值。这种情况下,我们最好将这部分共享的状态提升至他们最近的父组件当中进行管理。
下面就是例子:
渲染两个input,改变其中一个input的值,另一个也会同步改变
import React, {Component} from 'react'
import ReactDOM from 'react-dom'
class Child extends Component {
render() {
const parentMethod = this.props.onChange;
const value = this.props.value;
return (
<div>
<input type={'text'} onChange={this.transmitChangeValue.bind(this,parentMethod)} value={value}/>
<br></br>
</div>
);
}
transmitChangeValue(parentMethod,event) {
event.persist();
console.log(event);
let text = event.target.value;
parentMethod(text);
}
}
class Parent extends Component {
constructor() {
super();
this.state ={
value:"初始value"
}
}
onChange(text){
this.setState({
value:text
});
}
render() {
return (
<div>
<Child onChange={this.onChange.bind(this)} value={this.state.value}/>
<Child onChange={this.onChange.bind(this)} value={this.state.value}/>
</div>
)
}
}
ReactDOM.render(
<Parent/>,
document.getElementById('container')
)
共享状态提升的核心有以下几点:
- 父组件中需要定义一个改变state的函数
- 父组件调用子组件的时候需要将改变state的函数以及对象引用通过props传递给子组件
- 子组件在改变共享状态的时候,需要通过调用父组件传递过来的函数,来改变父组件的state值
这样就会形成一个链:
某个子组件改变了共享状态-》通知父组件改变state-》父组件state改变,重新渲染各个子组件-》其他子组件共享了状态,同步改变