基础应用
一个可以搜索、删除书籍的单页面:
基础代码以create react app生成,使用ES6的语法特征进行重构。
基础代码
整个页面不做任何组件拆分,只有一个BookListing组件:
const list=[
{
title: 'Road to React',
url: 'https://facebook.github.io/react/',
author: 'Jordan Walke',
num_comments: 3,
points: 4,
objectID: 0,
},
...
];
const isSearched = function(searchTerm){
return function(item) {
return item.title.toLowerCase().includes(searchTerm.toLowerCase());
}
}
export default class BookListing extends Component {
constructor(props) {
super(props);
this.state = {
list,
searchTerm: ''
};
this.search = this.search.bind(this);
this.dismiss = this.dismiss.bind(this);
}
search(e) {
this.setState({
searchTerm: e.target.value
});
}
dismiss(objectID) {
let updatedList = this.state.list.filter(item => item.objectID !== objectID);
this.setState({
list: updatedList
});
}
render() {
return (
<div>
<form>
Search <input
type="text"
onChange={e => this.search(e)}
>
</input>
</form>
<div>
{this.state.list.filter(isSearched(this.state.searchTerm))
.map(item =>
<div key={item.objectID} className="table-row">
<span style={{ width: '40%' }}>
<a href={item.url}>{item.title}</a>
</span>
<span style={{ width: '30%' }}>{item.author}</span>
<span style={{ width: '10%' }}>{item.num_comments}</span>
<span style={{ width: '10%' }}>{item.points}</span>
<span style={{ width: '10%' }}>
<button
type="button"
onClick={() => this.dismiss(item.objectID)}
>
Dismiss
</button>
</span>
</div>
)}
</div>
</div>
)
}
}
重构实践
- ES6箭头函数
isSearched函数可以使用ES6的箭头函数语法进行重构,改写后:
const isSearched = searchTerm => item => item.title.toLowerCase().includes(searchTerm.toLowerCase())
仅用一行就可以完成同样的功能。
- 解构
ES6支持对象解构,render函数中的state可以解构,简化链式调用的长度:
render() {
const {list, searchTerm} = this.state;
// ...
{list.filter(isSearched(searchTerm))
.map(item =>
<div>
{/* ... */}
</div>
)}
}
- 组件拆分
BookListing组件过于复杂,可以拆分成几个职责更单一的组件,提取Search,Table组件,对应于搜索框和列表:
export default class Search extends Component {
render() {
const {value, search, children} = this.props;
return (
<form>
Search <input
type="text"
onChange={search}
>
</input>
</form>
)
}
}
export default class Table extends Component {
render() {
const { list, pattern, onDismiss } = this.props;
return (
<div>
{list.filter(isSearched(searchTerm))
.map(item =>
<div key={item.objectID} className="table-row">
<span style={{ width: '40%' }}>
<a href={item.url}>{item.title}</a>
</span>
<span style={{ width: '30%' }}>{item.author}</span>
<span style={{ width: '10%' }}>{item.num_comments}</span>
<span style={{ width: '10%' }}>{item.points}</span>
<span style={{ width: '10%' }}>
<button
type="button"
onClick={() => this.dismiss(item.objectID)}
>
Dismiss
</button>
</span>
</div>
)}
</div>
)
}
}
改写顶层组件如下:
export default class extends Component {
//...
render() {
<div className="page">
<Search
value={searchTerm}
onChange={this.search}
/>
<Table
list={list}
pattern={searchTerm}
onDismiss={this.dismiss}
/>
</div>
}
}
button也可以抽取成为单独的组件:
export default class Button extends Component {
render() {
return(
<button
type="button"
onClick={onClick}
>
{value}
</button>
)
}
}
- 使用children属性
React组件的props提供了children属性,代表组件的子元素,可以对Search,Button组件使用:
<Search
value={searchTerm}
onChange={this.search}
>
Search
</Search>
// use children
export default class Search extends Component {
render() {
const {value, onChange, children} = this.props;
return (
<form>
{children} <input
type="text"
onChange={onChange}
>
</input>
</form>
}
}
- 引入无状态组件
以上提取的3个组件,基本都不需要维护自身状态,可以改写为无状态组件:
export default function Search(props) {
const { value, onChange, children } = props;
return (
<form>
{children} <input
type="text"
value={value}
onChange={onChange}
/>
</form>
)
}
另外两个组件也可以做同样的处理。无状态组件除了语法简洁、便于测试等优点外,无状态的特性使得它没有React组件的生命周期管理,因此在渲染时相比于React组件有更好的性能。
- 更纯的函数式组件
props可以通过解构直接当做函数参数,然后直接加上箭头函数进行改造,省掉函数内部的props参数解析,并且直接去掉return关键字:
export default ({ value, onChange, children }) =>
<form>
{children} <input
type="text"
value={value}
onChange={onChange}
/>
</form>
以上处理也同样适用于另外两个提取的组件。