借鉴React文档 “React的编程思想”章节,实现一个类似百度搜索的搜索框组件
文档中的步骤为
- 构建组件层次
- 用React构建一个静态版本
- 确定state的最小表示
- 确定state的位置
- 添加反向数据流
一、构建组件层次
结构如下
-SearchBox
-SearchInput
-SearchResult
-SearchResultList
因后续望在下拉框显示“加载中”“已全部加载” “无数据”等加载状态,故在SearchResult组件下,将搜索结果列表组件SearchResultList独立出来,方便后续加入状态组件到SearchResult下
二、构建静态版本
这一步,没有交互,故暂不考虑state,数据通过props从父组件向子组件传递,组件中仅有render()方法,参照文档,定义了一个数据列表,作为props传入顶级组件SearchBox,而后,SearchBox再将其传入需要该列表的子组件,及SearchResult。这里只是用于显示静态界面,编写样式,因后续改为调用Github API获取数据,该定义的数据变量及相关的组件属性最终将被删除。
这一步我采用自上而下的方式构建。
SearchBox定义如下
class SearchBox extends React.Component {
render() {
return (
<div className={styles.searchBox}>
<SearchInput />
<SearchResult resultList={this.props.dataset} />
</div>
);
}
}
var DATASET = [
{name: 'Wanna Be Starting Something', id: '0'},
{name: 'Baby be Mine', id: '1'},
{name: 'The Girl is Mine', id: '2'},
{name: 'Thriller', id: '3'},
{name: 'Beat It', id: '4'},
{name: 'Bilie Jean', id: '5'}
];
ReactDOM.render(
<SearchBox dataset={DATASET}/>,
document.getElementById('root')
);
SearchResult 和 SearchResultList结构如下
可以看到 SearchResult 将属性 resultList (来自父组件)传递给它的孩子 SearchResultList 的 属性 data。后续SearchResult 会调用API接口,将结果传入SearchResultList ,即SearchResultList 只是拿到数据做渲染。
SearchResultList 渲染列表的方式是调用了map方法,将每一个数据项包装在 <li> 元素中,最后返回一个 <ul> 列表
class SearchResultList extends React.Component {
render() {
const resultItems = this.props.data;
const listItems = resultItems.map((item) =>
<li key={item.id}>
{item.name}
</li>
);
return (
<ul>{listItems}</ul>
);
}
}
class SearchResult extends React.Component {
render() {
return (
<div>
<SearchResultList data={this.props.resultList} />
</div>
);
}
}
三、确定state的最小表示
和文档不同的是,后来将数据列表设置为state。原文档的初始数据作为顶级父组件的props传入,筛选结果是初始数据和用户输入计算后得出,而我最终的数据列表是服务器端数据根据用户输入筛选后返回,且是不断变换的,故设计为 SearchResult 的 state
最终的 state 是:
- Input 输入框的值
- 调用API得到的数据
- loading 状态
四、state 的位置
因 SearchInput 需要输入框的值用以显示,SearchResult 需要输入框的值用以请求数据,故将之放在需要它的组件的最小公共父组件里,即 SearchBox。搜索结果列表和 loading状态放在SearchResult下
五、添加反向数据流
因输入框的值(props 属性)来自 父组件的 state, 要想输入框响应用户输入,即显示用户的输入,input 需要设置父组件的state, 即将值传递给父组件。实现方式为父组件向子组件传递 props 进行通讯,只是父组件传递的,是作用域为父组件自身的函数,子组件调用该函数,将子组件想要传递的信息,即输入框的值,作为参数,传递到父组件的作用域中。
参考: http://taobaofed.org/blog/2016/11/17/react-components-communication/
最终代码如下:
import $ from 'jquery';
import React from 'react';
import ReactDOM from 'react-dom';
import styles from './index.css';
import registerServiceWorker from './registerServiceWorker';
class SearchResultList extends React.Component {
render() {
const resultItems = this.props.data;
const listItems = resultItems.map((item) =>
<li key={item.id}>
{item.name}
</li>
);
return (
<ul>{listItems}</ul>
);
}
}
class SearchResult extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: true,
error: null,
data: null
};
}
componentWillUpdate() {
$.ajaxSetup({
async: true
});
$.getJSON('https://api.github.com/search/repositories?q='+this.props.searchText, (result) => {
console.log(typeof(result));
console.log(result.items);
this.setState({loading: false, data: result.items});
});
}
render() {
if(this.state.data) {
return (
<div>
<SearchResultList data={this.state.data} />
</div>
);
}
else {
return (
<div></div>
);
}
}
}
class SearchInput extends React.Component {
constructor(props) {
super(props);
this.handleSearchTextInputChange = this.handleSearchTextInputChange.bind(this);
}
handleSearchTextInputChange(e) {
this.props.onSearchTextInput(e.target.value);
}
render() {
return (
<form>
<input
type="text"
placeholder="Search..."
value={this.props.searchText}
onChange={this.handleSearchTextInputChange}
/>
</form>
);
}
}
class SearchBox extends React.Component {
constructor(props) {
super(props);
this.state = {
searchText: ''
};
this.handleSearchTextInput = this.handleSearchTextInput.bind(this);
}
handleSearchTextInput(searchText) {
this.setState({
searchText: searchText
});
}
render() {
return (
<div className={styles.searchBox}>
<SearchInput
searchText={this.state.searchText}
onSearchTextInput={this.handleSearchTextInput}
/>
<SearchResult
searchText={this.state.searchText}
/>
</div>
);
}
}
ReactDOM.render(
<SearchBox />,
document.getElementById('root')
);
registerServiceWorker();
结果如下
和在 github 的搜索结果保持一致
后续优化:
1. 将请求 url 提取出来作为props传递给顶级父组件
2. 增加搜索结果列表的搜索状态样式,分次请求
3. 还需学习组件生命周期,现数据请求和设置state放在componentWillUpdate中,会造成重复请求的问题。