作者名:木木
六、组件之状态与通信
在React.js中,定义一个组件的最简单的方式就是 函数
1> 函数式组件:
function Kaikeba() {
return (
<div>
<h2>开课吧!</h2>
</div>
);
}
ReactDOM.render(
<Kaikeba />,
document.getElementById('app')
);
// 函数的名称就是组件的名称,必须是首字母大写
// 函数的返回值就是组件要渲染的内容
2> 类组件:
class Miaov extends React.Component {
render() {
return (
<div>
<h2>妙味!</h2>
</div>
);
}
}
// 组件类必须继承 React.Component
// 组件类必须有 render方法
// render 方法的返回值就是组件要渲染的内容
// 类的名称(组件的名称),也必须是首字母大写
3> 组件复用 - 数据传入
使用组件的时候通过 标签属性 -property 的方式传入数据,在组件内部通过构造函数参数(如果是函数组件,则通过函数参数)来接收传入的数据
<组件名称 属性名称="值" />
// 使用表达式
<组件名称 属性名称={表达式} />
ReactDOM.render(
<FriendList datas={datas} />,
document.getElementById('app')
);
4> 接收参数 -props
- 函数式组件:通过函数的第一个参数来接收;
- 类式组件:通过类的 props 属性接收;
无论是函数式组件还是类式组件,都会把传入的参数封装成一个对象
<组件名称 属性名称1="值1" 属性名称二={表达式二} />
// 函数式组件:
// 函数创建的组件是无状态组件,没有state,没有生命周期方法,它是一种只负责展示的纯组件
function 组件名称(参数) {
// 参数的结构为:
参数 = {
属性名称1: "值1",
属性名称二: 表达式二的值
}
return <div>组件结构</div>
}
// 类式组件
class 组件名称 extends React.Component {
constructor(参数) {
super(参数);
this.props = {
属性名称1: "值1",
属性名称2: 表达式二的值
}
}
render() {
return <div>组件结构</div>
}
}
// 在类式组件中,需要注意:
// 1.当子类重写 constructor,则必须调用父类 super
// 2.把接收到的参数传入父类构造函数,父类构造函数中会创建一个对象属性:props 类存储传入的参数数据
5> 通过参数动态渲染组件结构
class FriendList extends React.Component {
constructor(props) {
super(props);
}
render() {
let {datas} = this.props;
return (
<div className="friend-list">
{Object.keys(datas).map((key, index) => (
<div className="friend-group" key={key}>
<dt>{datas[key].title}</dt>
{datas[key].list.map((list, index) => (
<dd key={index}>{list}</dd>
)}
</div>
))}
</div>
);
}
}
6> 子组件提取
组件与类一样,是一个不断提取的过程,当我们发现某个部分可复用或者结构复杂的时候,我们可以对它再次进行提取
class FriendGroup extends React.Component {
constructor(props) {
super(props);
}
render() {
let {data} = this.props;
return (
<div className="friend-group">
<dt>{data.title}</dt>
{data.list.map((list, index) => {
<dd key={index}>{list}</dd>
})}
</div>
);
}
}
class FriendList extends React.Component {
constructor(props) {
super(props);
}
render() {
let {datas} = this.props;
return (
<div className="friend-list">
{Object.keys(datas).map((key, index) => (
<FriendGroup data={datas[key]} key={key} />
))}
</div>
);
}
}
7> 组件状态
-
组件状态是什么? 状态被用来存储组件在某段时间内状态改变的信息。用户事件或系统事件会导致一些经典的状态改变。(比如:对用户输入的回应、服务器的请求、生命周期函数)
-
组件 state 工作是这样的:先给组件设置一个默认状态,再获取当前状态,最后更新这个状态
-
修改 state 的正确姿势: 不能直接修改state 在React中,直接修改state并不会触发render函数,所以下面的写法是错误的。
this.state.title = 'React'; // 错误
// 组件的State只能通过setState()方式进行修改。例如:
this.setState({title: 'React'}); // 正确
- setState方法由父类React.Component提供,当该方法被调用时,组件的state会被更新,同时会被重新调用组件的render方法对组件进行渲染。
- 组件构造函数是唯一可以对 state 直接赋值(初始化)的位置
8> 绑定事件
class FriendGroup extends React.Component {
constructor(props) {
super(props);
this.state = {
expanded: false
};
this.expand = this.click.expand();
}
expand() {
this.setState({
expanded: !this.state.expanded
});
}
render() {
let {data} = this.props;
let {expanded} = this.state;
return (
<div className={["friend-group",expanded && "expanded"].join(' ')}>
<dt onClick={this.expand}>{data.title}</dt>
{data.list.map((list, index) => {
<dd key={index}>{list}</dd>
})}
</div>
);
}
}
React.js的事件绑定需要注意:
- 事件名称是驼峰命名的
- 事件绑定函数的this指向
- 通过bind改变this指向,为了能够在方法中调用对象的属性和其他方法, 我们需要把this指向组件
- 通过箭头函数处理
<dt onClick={e=>{this.expand}}>{data.title}</dt>
如上代码: 事件绑定函数的第一个参数是事件对象 因为this指向了组件对象,那么获取当前事件触发元素,可以通过e.target来获取
9> 获取原生DOM对象 有的时候我们也是需要操作原生DOM对象的,除了可以通过事件源来获取,还可以通过ref的方式来获取
class MyComponent extends React.Component {
render() {
return (
<div>
<input ref={el=>this.el=el} />
<button onClick={this.todo.bind(this)}>Button</button>
</div>
);
}
todo() {
console.log(this.el.value);
}
}
10> 更新异步 出于性能考虑,setState方法的修改并不是立即生效的
// this.state.val = 0
this.setState({
val: this.state.val + 1
});
// this.state.val 的值并不是 1
console.log(this.state.val);
11> 更新合并 React会把多个setState合并成一个调用
// this.state.val = 0
this.setState({
val: this.state.val + 1
});
this.setState({ // 因为异步的问题,this.state.val 的值在这里还是0
val: this.state.val + 1
});
12> props与state的区别
- state的主要作用是用于 组件保存、控制、修改自己的可变状态,在组件内部进行初始化,也可以在组件内部进行修改,但是组件外部不能修改组件的state
- props的主要作用是让使用该组件的父组件可以传入参数来配置该组件,它是外部传进来的配置参数,组件内部无法控制也无法修改
- state和props都可以决定组件的外观和显示状态。通常,props作为不变数据或者初始化数据传递给组件,可变状态使用state
- 能使用props就不要使用state
13> 无状态组件
- 没有状态的组件,我们成为 无状态组件。
- 因为状态会带来复杂性,所以,通常我们推荐使用无状态组件。
- 函数式组件没有state,所以通常我们编写使用函数式组件来编写无状态组件
14> 数据流
- 在React.js中,数据是从 上自下流动(传递) 的,也就是一个父组件可以把它的state/props通过props传递给它的 子组件 ,但是 子组件不能修改props。
- React.js是单向数据流,如果子组件需要修改父组件状态(数据),是通过 回调函数 方式来完成的。
15> React组件通信方式汇总:
需要组件之前进行通信的几种情况:
- 父组件向子组件通信
- 子组件向父组件通信
- 跨级组件通信
- 没有嵌套关系组件之间的通信
1.父组件向子组件通信
父组件通过向子组件传递props,子组件得到props后进行相应的处理

2.子组件向父组件通信
3.跨级组件通信
使用context context是一个全局变量,像是一个大容器,在任何地方都可以访问到,我们可以把要通信的信息放在context上,然后在其他组件中可以随意取到
使用context也很简单,需满足:
- 1、上级组件要声明自己支持context,提供context中属性的PropTypes,并提供一个函数来返回相应的context对象
- 2、子组件要声明自己主要使用context,并提供其需要使用的context属性的PropTypes
- 3、父组件需提供一个getChildContext函数,以返回一个厨师的context对象 如果组件中使用构造函数(constructor),还需要在构造函数中传入第二个参数context,并在super调用父类构造函数时传入context,否则会造成组建中无法使用context。
下面例子中的组件关系:ListItem是List的子组件,List是app的子组件
4.没有嵌套关系的组件通信(兄弟组件)
使用自定义事件机制
实现这样一个功能: 点击List2中的一个按钮,改变List1中的信息显示(List1和List2没有任何嵌套关系,App是他们的父组件)
- 首先需要项目中安装events包:
npm install events --save
- 在src下新建一个util目录里面见一个events.js
import { EvenetEmitter } from 'events';
export default new EventEmitter();
- list1.js
import React, { Component } from 'react';
import emitter from '../util/events';
class List extends Component {
constructor(props) {
super(props);
this.state = {
message: 'List1',
};
}
componentDidMount() {
// 组件装载完成以后声明一个自定义事件
this.eventEmitter = emitter.addListener('changeMessage', (message) => {
this.setState({
message,
});
});
}
componentWillUnmount() {
emitter.removeListener(this.eventEmitter);
}
render() {
return (
<div>
{this.state.message}
</div>
);
}
}
export default List;
- List2.js
import React, { Component } from 'react';
import emitter from '../util/events';
class List2 extends Component {
handleClick = (message) => {
emitter.emit('changeMessage', message);
};
render() {
return (
<div>
<button onClick={this.handleClick.bind(this, 'List2')}>点击我改变List1组件中显示信息</button>
</div>
);
}
}
- APP.js
import React, { Component } from 'react';
import List1 from './components/List1';
import List2 from './components/List2';
export default class App extends Component {
render() {
return (
<div>
<List1 />
<List2 />
</div>
);
}
}
- 自定义事件是典型的发布订阅模式,通过向事件对象上添加监听器和触发事件来实现组件之间的通信