React 的核心思想就是使用组件化的思想来开发出一个个独立的组件,然后将组件再拼装成一个完整的应用,从 MVC
分层思想上来考虑的话,React 专注在 UI 展现层上。
在 React 的骨架 JSX 一文中,有详细的讲解过使用 JSX
来开发组件的结构,也提到了使用 Inline Style
可以给组件定义样式,这样能确保组件的独立性。除了结构和样式,一个独立完整的组件还包含一些其他的部分。
从一个简单的 Dropdown 组件来开启我们的 React 组件化之路。
首先需要说明的是,Demo 的代码都是 ES6
的语法,之后的 React 文章中用到的 JavaScript 代码都将使用 ES6
的语法,如果你还不了解 ES6
,建议先了解一下它的语法。另外考虑到 Demo 代码量的问题忽略了组件的样式部分,假设样式是通过外链或 Inline Style
的形式加载进来的。
// dropdown.js
class Dropdown extends React.Component {
constructor (props) {
super(props);
this.state = {
visibile: false
};
}
onClick = () => {
this.setState({
visible: !this.state.visible
});
}
render () {
const displayValue = this.state.visible ? 'block' : 'none';
return (
<div className="dropdown">
<button className="btn btn-default" onClick={this.onClick}>Browsers</button>
<ul className="dropdown-menu" style={{display: displayValue}}>
<li><a href="#">Firefox</a></li>
<li><a href="#">Chrome</a></li>
<li><a href="#">Safari</a></li>
<li><a href="#">Opera</a></li>
<li><a href="#">Internet Expoler</a></li>
</ul>
</div>
)
}
};
export default Dropdown;
React 的组件使用 状态机
的概念来描述组件本身的可变数据部分。Dropdown 组件最基本的用户交互逻辑可以抽象成两个状态 收起
和 展开
,进一步用代码来描述就是 visible: false(收起)
和 visible: true(展开)
。
要获取组件的状态都需要通过 this.state
来获取,在 constructor(构造函数)
中,可以通过 this.state
来设置组件的初始状态。
要改变状态就必须通过 this.setState
方法来改变,不能通过直接修改 this.state.xxx = "xxx"
来进行修改,因为 this.state
的值是 immutable(不可变)
的。可以认为 state
有自己的 setter
和 getter
方法,这样能更好的理解。
将 visible: false/true
的状态用于关联到菜单的展开和收起的两种样式,点击的时候通过切换两种状态,React 根据状态来自动更新 UI。
如果按照传统的开发逻辑,我们要控制 Dropdown 菜单的收起和展开,需要先获取到菜单的 DOM 元素,然后在点击时,直接改变 DOM 元素的样式。
React 使用状态机来简化了繁琐的 DOM 操作,传统开发模式中繁琐的 DOM 操作都可以转变成状态的改变,底层的 DOM 操作由 React 来接管。如果你在使用 React 开发应用的时候想着应该怎么操作 DOM,这时候需要反思一下是不是使用 React 的方式有问题。
如果要把 Dropdown 组件做成公用的,我们需要做进一步的改进和优化。Dropdown 的按钮内容以及菜单的内容都需要依赖父组件来传递。
父组件将数据传递给子组件,这个时候就需要通过 props
来传递了。
// app.js
// 加载Dropdown组件
import Dropdown from './dropdown';
class App extends React.Component {
constructor (props) {
super(props);
}
static defaultProps = {
dropdownBtn: 'Browsers',
dropdownItems: [
'Firefox',
'Chrome',
'Safari',
'Opera',
'Internet Expoler'
]
}
render () {
<div>
<h1>This is a Dropdown demo.</h1>
<Dropdown
btnText={this.props.dropdownBtn}
items={this.props.dropdownItems} />
</div>
}
};
App 组件中需要用到 Dropdown 组件,先使用 import
来加载该组件。将 Dropdown 需要用到的数据存储到 App 组件的默认的 props
中。
// 组件默认的props都可以存放到这里
static defaultProps = {...}
然后使用属性声明的方式传递给 Dropdown 组件。
// this.props包含了App中所有的属性
<Dropdown btnText={this.props.dropdownBtn} items={this.props.dropdownItems} />
Dropdown 在接收到来自父组件的数据,也是通过 this.props
来访问。这里需要注意父子组件都有 props
,都能通过 this.props
来访问,父组件是组件内部默认定义的,而子组件的是来自父组件的传递。
在 Dropdown 中使用父组件传递过来的 props
,将原来的 render 方法稍做改进。
render () {
const displayValue = this.state.visible ? 'block' : 'none';
return (
<div className="dropdown">
<button className="btn btn-default" onClick={this.onClick}>{this.props.btnText}</button>
<ul className="dropdown-menu" style={{display: displayValue}}>
{
this.props.items.map((item) => (
<li key={`item-{item}`}>
<a href="#">{item}</a>
</li>
))
}
</ul>
</div>
)
}
子组件在接受父组件的数据时,可以对数据类型进行校验,并且可以校验数据是否可选,如果传递的数据结果不匹配,React 会给出提示,这对于应用的健壮性来说是一个非常好的设计,建议组件在接收数据的时候最好都能进行数据校验,这样能避免很多因为数据格式问题引起的异常。
需要注意的是,在遍历数组生成结构的时候,都需要加上 key
, 用于标示该元素的唯一性。
在 Dropdown 的组件中,这些数据都是必须传递的,可以指定校验为必需。
static propTypes = {
// 指定按钮内容是必需的,并且类型为字符串
btnText: React.PropTypes.isRequired.string,
// 指定items的数据是必需的,并且类型为数组
items: React.PropTypes.isRequired.array
}
更多的数据类型说明,请参见属性校验的官方文档。
React 有自己实现的事件系统,该事件系统是基于标准 W3C 的 DOM 事件,并在此基础上对事件进行了封装,抹平了不同浏览器之间兼容性差异。为 Virtual DOM
绑定事件,直接在元素上使用驼峰式的属性声明的方式即可。
<button onClick={this.onClick}>{this.props.btnText}</button>
...
// event对象也传递给了事件处理函数
onClick = (event) => {
// console.log(event.type) => click
this.setState({
visible: !this.state.visible
});
}
在事件处理函数中,其 this
并不指向组件类,可以通过在构造函数中使用 bind
来改变,也可以用最简单的 ES6
的箭头函数。
想查看的 React 的事件系统支持哪些类型的事件,或者 event
对象都包含了哪些可用的属性,可以查看关于事件的官方文档。
React 的组件有几大生命周期,在这些生命周期内,组件本身提供了一些事件接口,这些事件和 Virtual DOM
的事件不一样,是组件本身会触发的事件。
生命周期按照阶段来划分的话,按照先后顺序分为 5 个阶段:
state
和 props
来初始化组件);render
);mount
,装在前和装载完毕都有相应的事件);state
和props
的更新,都会触发相应的事件);unmount
组件销毁);再回到上面的 App 组件中,如果 Dropdown 的数据是依赖于服务器异步数据的话,可以利用组件的生命周期事件来加载数据。
给 App 组件增加一个 componentWillMount
事件,该事件会在组件即将装载的时候触发。
// app.js
import Dropdown from './dropdown';
class App extends React.Component {
constructor (props) {
super(props);
// 设置一个空的state对象
// 当第一次render的时候,直接使用state不会报错
this.state = {};
}
componentWillMount () {
// 使用fetch来获取一个数据
fetch('http://xxx.com/data')
.then((response) => response.json())
.then((response) => {
// 数据加载成功后通过setState改变状态
this.setState({
dropdownBtn: response.dropdownBtn,
dropdownItems: response.dropdownItems
});
});
}
render () {
// 第一次render的时候state并没有数据
// 当数据加载成功调用了setState后才会有数据,这时组件会重新render
<div>
<h1>This is a Dropdown demo.</h1>
<Dropdown
btnText={this.state.dropdownBtn}
items={this.state.dropdownItems} />
</div>
}
}
App 组件传递给 Dropdown 将原来的静态数据改成了动态数据,所以要将 props
换成 state
。Dropdown 组件也需要将原来的数据校验做一些调整。
最终的 Dropdown 完整的代码是下面这样的。
// dropdown.js
class Dropdown extends React.Component {
constructor (props) {
super(props);
this.state = {
visibile: false
};
}
static propTypes = {
// 指定按钮内容是必需的,并且类型为字符串
dropdownBtn: React.PropTypes.string,
// 指定items的数据是必需的,并且类型为数组
dropdownItems: React.PropTypes.array
}
onClick = (event) => {
// console.log(event.type) => click
this.setState({
visible: !this.state.visible
});
}
render () {
const displayValue = this.state.visible ? 'block' : 'none';
return (
<div className="dropdown">
<button className="btn btn-default" onClick={this.onClick}>{this.props.btnText}</button>
<ul className="dropdown-menu" style={{display: displayValue}}>
{
this.props.items.map((item) => (
<li key={`item-{item}`}>
<a href="#">{item}</a>
</li>
))
}
</ul>
</div>
)
}
}
而要将组件放到页面中渲染,可以通过 React.render
来调用。
<div id="root"></div>
...
React.render(
<App />,
document.getElementById('root')
);
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8