组件允许我们将代码拆分为独立可复用的代码片段,这是一个十分重要的概念
1、函数组件
我们可以通过编写 JavaScript 函数定义组件
<!DOCTYPE html>
<html>
<head>
<title>Demo</title>
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone"></script>
</head>
<body>
<div id="app"></div>
<script type="text/babel">
// 通过 function 定义组件,函数返回一个 React 元素
// 需要注意的是,组件名称必须以大写字母开头
// 因为 React 会将以小写字母开头的组件视为原生 DOM 标签
function SayHello() {
return <h1>Hello World</h1>;
};
// React 元素不仅仅是 DOM 标签,也可以是自定义组件
const element = <SayHello />;
ReactDOM.render(
element,
document.getElementById('app')
);
</script>
</body>
</html>
2、class 组件
我们也可以通过使用 class 定义组件,并且推荐使用这种方式
因为使用 class 定义组件允许我们为组件添加一些额外的方法和属性,使得组件具有更好的拓展性
<!DOCTYPE html>
<html>
<head>
<title>Demo</title>
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone"></script>
</head>
<body>
<div id="app"></div>
<script type="text/babel">
// class 组件必须继承 React.Component
class SayHello extends React.Component {
// 使用 render() 函数返回一个 React 元素
// 注意,该元素必须只能具有一个顶层标签
render() {
return <h1>Hello World</h1>;
}
};
// 使用组件的方法是一样的
const element = <SayHello />;
ReactDOM.render(
element,
document.getElementById('app')
);
</script>
</body>
</html>
3、props
当 React 元素为自定义组件时,JSX 接收的属性会转换为单个对象传递给组件,我们将这个对象称之为 props
通过这种方式,我们可以给组件传递数据
class SayHello extends React.Component {
constructor(props) {
// 为什么要调用 super?
// 如果不调用 super,那就无法在构造函数中访问 this
// 为什么要传入 props?
// 如果不传入 props,那就无法在构造函数中访问 this.props
super(props);
console.log(this);
console.log(this.props);
}
render() {
// 在组件中通过 this.props 访问 props
// 注意,props 应该是只读的,也就是说我们不应该修改它的取值
return <h1>Hello { this.props.name }</h1>;
}
};
// 这里将属性转换为 props 对象并传入组件
const element = <SayHello name='Alice' />;
ReactDOM.render(
element,
document.getElementById('app')
);
4、自定义函数
通过自定义函数,我们可以在 class 组件中添加额外的方法
class SayHello extends React.Component {
// 构造函数
constructor(props) {
super(props);
}
// 自定义函数
format(name) {
return name.substring(0,1).toUpperCase() + name.substring(1);
}
render() {
// 通过 this.functionName() 调用自定义函数
let formatedName = this.format(this.props.name);
return <h1>Hello { formatedName }</h1>;
}
};
const element = <SayHello name='alice'/>;
ReactDOM.render(
element,
document.getElementById('app')
);
5、state
通过 state,我们可以在 class 组件中添加额外的数据
state 与 props 都是组件的数据,不同之处在于 props 是从父组件传入的数据,而 state 是本组件私有的数据
class Timer extends React.Component {
constructor(props) {
super(props);
// 初始化 state
this.state = { date: new Date() };
}
render() {
// 通过 this.state 访问 state
return (
<div>
<h1>Hello</h1>
<p>现在是{ this.state.date.toLocaleTimeString() }</p>
</div>
)
}
};
const element = <Timer />;
ReactDOM.render(
element,
document.getElementById('app')
);
使用 state 有几个需要特别注意的地方:
- 不要直接修改 state
我们可以直接访问 state,但是不能直接修改 state,例如
this.state.date = new Date()
// 这样的语句并不会重新渲染组件
要想修改 state 并重新渲染组件,我们可以使用 this.setState()
,例如
this.setState({date: new Date()})
但是构造函数是唯一特别的地方,我们可以直接在构造函数里面给 this.state
赋值,例如
this.state = { date: new Date() }
- state 的更新是异步的
出于性能的考虑,React 可能会把多个 setState()
调用合并成一个调用
因此,this.props
和 this.state
可能会异步更新,所以不要依赖它们的值来更新 state
this.setState({
counter: this.state.counter + this.props.increment
})
// 这样的代码可能不会正常更新 counter
要解决这个问题,可以让 setState()
接收一个函数作为参数,而不是一个对象
这个函数的第一个参数为上一个 state,第二个参数为此次更新被应用时的 props,返回一个对象
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
- state 的更新会被合并
setState()
调用会把提供的对象合并到当前 state,而非替换
也就是说,假如我们在构造函数中初始化 state 如下:
constructor(props) {
super(props);
this.state = {
name: 'Alice',
phone: '12345679810'
};
}
然后调用 setState()
方法更新 state 如下:
this.setState({
phone: '10987654321'
})
此时,state 中的数据应该是 { name: 'Alice', phone: '10987654321'}
,而不是 { phone: '10987654321' }
6、生命周期
每个组件都有生命周期,都会包含生命周期方法,我们可以通过重写这些方法,使得组件在特定阶段完成特定操作
组件的生命周期大体可以分为三个阶段,每个阶段调用生命周期方法的顺序如下:
挂载:当组件插入到 DOM 中时触发
-
constructor()
:在组件挂载前调用,主要用于初始化 state 或为事件处理函数绑定实例这里不要使用
setState()
方法,如果需要初始化 state,可以直接给this.state
赋值 -
render()
:class 组件中唯一必须实现的方法,该函数应该为纯函数也就是说在不修改 state 的情况下,每次调用返回相同的结果,并且它不与浏览器进行交互
-
componentDidMount()
:在组件挂载后调用,主要进行依赖于 DOM 结点的初始化操作例如实例化请求或添加订阅,这里可以使用
setState()
方法
更新:当组件的 props 或 state 发生改变时触发
-
render()
-
componentDidUpdate()
:在更新之后调用,但是首次渲染不会执行这里可以进行 DOM 操作,并且可以使用
setState()
方法,但必须包含在一个条件语句里面
卸载:当组件从 DOM 中移除时触发
-
componentWillUnmount()
:在组件卸载之前调用这里不应使用
setState()
方法,因为组件卸载之后,将永远不会重新渲染,也不会重新挂载
class Timer extends React.Component {
// 生命周期方法,在挂载前调用
constructor(props) {
super(props);
// 初始化 state
this.state = { date: new Date() };
}
// 自定义方法
tick() {
// 通过 this.setState() 修改 state
this.setState({
date: new Date()
})
}
// 生命周期方法,在挂载后调用
componentDidMount() {
this.timerID = setInterval(() => this.tick(), 1000)
}
// 生命周期方法,在卸载前调用
componentWillUnmount() {
clearInterval(this.timerID)
}
// 生命周期方法,在挂载或更新时调用
render() {
// 通过 this.state 访问 state
return (
<div>
<h1>Hello</h1>
<p>现在是{ this.state.date.toLocaleTimeString() }</p>
</div>
)
}
};
const element = <Timer />;
ReactDOM.render(
element,
document.getElementById('app')
);
【 阅读更多 React 系列文章,请看 React学习笔记 】