您可以通过左右滑屏查看更多笔记内容。。
状态提取:将子状态(substate)从一个组件移动到其他组件中的重构过程被称为状态提取。
状态提升:状态提取的过程也可以反过来,从子组件到父组件,这种情形被称为状态提升。
案例:
将状态和有关排序的方法从 App 组件向下移动到 Table 组件中。
import React, { Component } from 'react';
// import logo from './logo.svg';
import './App.css';
import {sortBy} from 'lodash';
// 设置好 URL 常量和默认参数,将 API 请求分解成几步
const DEFAULT_QUERY = 'redux';
// 每次抓取的资讯数量
const DEFAULT_HPP='10';
const PATH_BASE = 'https://hn.algolia.com/api/v1';
const PATH_SEARCH = '/search';
const PARAM_SEARCH = 'query=';
const PARAM_PAGE = 'page=';
const PARAM_HPP='hitsPerPage=';
const Loading =()=><div>Loading...</div>
// 定义一个排序函数,用于返回未排序的列表
const SORTS={
NONE:list=>list,
TITLE: list=>sortBy(list,'title'),
AUTHOR: list=>sortBy(list,"author"),
COMMENTS: list=>sortBy(list,'num_comments').reverse(),
POINTS:list=>sortBy(list,'points').reverse(),
};
// function withFoo(Component){
// return function(props){
// return <Component{...props}/>
// }
// }
// const withFoo=(Component)=>(props)=><Component{...props}/>
//当加载状态 (isLoading) 为 true 时,组件显示 Loading 组件,否则显示输入的组件。
const withLoading =(Component)=>({isLoading,...rest})=>
isLoading?
<Loading/>
:<Component{...rest}/>
// 使用模板字符串(template strings)进行链接字符
// const url = `${PATH_BASE}${PATH_SEARCH}?${PARAM_SEARCH}${searchTeam}&${PARAM_PAGE}`;
// console.log(url);
// 使用真实的API
class App extends Component{
constructor(props){
super(props);
this.state={
// 用来存储每次能够查询到的内容
results:null,
searchKey:'',
searchTeam:DEFAULT_QUERY,
// 存储错误信息的‘
error:null,
//存储加载状态
isLoading:false,
}
this.setSearchTopStories = this.setSearchTopStories.bind(this);
this.fetchSearchTopStories = this.fetchSearchTopStories.bind(this);
this.onSerachChange = this.onSerachChange.bind(this);
this.onDissmisss = this.onDissmisss.bind(this);
this.onSerachSubmit=this.onSerachSubmit.bind(this);
}
// 如果搜索的结果已经存在于缓存中,就阻止 API 请求。
needsToSearchTopStories(searchTeam){
return !this.state.results[searchTeam];
}
onSerachSubmit(event){
const {searchTeam}=this.state;
this.setState({searchKey:searchTeam})
// this.fetchSearchTopStories(searchTeam);
if(this.needsToSearchTopStories(searchTeam)){
this.fetchSearchTopStories(searchTeam);
}
event.preventDefault();
}
//删除函数
onDissmisss(id){
const {searchKey,results}=this.state;
const {hits,page}=results[searchKey];
// 找到除点击外的其他id
const isNotId=item=>item.objectID!==id;
//在原数组中筛选包含其他id的一个新的数组
const updatedHits=hits.filter(isNotId);
console.log(results);
// 重新设置state
this.setState({
results: {
...results,
[searchKey]: { hits: updatedHits, page }
}
});
}
//onChange函数,event对象的target属性中带有输入框的值
onSerachChange(event){
// 得到我输入框的内容
this.setState({searchTeam:event.target.value});
}
setSearchTopStories(result) {
// 获取result 中的hits 与 page
const {hits,page}=result;
// 判断老的hits字段是否存在,当页码为0时,hits 是空的
//当点击more 时,页码不为0,此时他是下一页,老的 hits 已经存在状态中等待着与新的分页合并。
const {searchKey,results}=this.state;
// 从组件状态中检索 searchKey,中
const oldHits=results&&results[searchKey]
? results[searchKey].hits:[];
// 合并老的hits.及API返回的新的hits,
const updatedHits=[
...oldHits,
...hits
]
//将合并后的 hits 和页码设置到本地组件的状态中。一个新的 result 可以设置在 results 集中
this.setState({
results: {
...results,
[searchKey]: { hits: updatedHits,page}
},
isLoading:false
});
}
fetchSearchTopStories(searchTeam,page=0) {
this.setState({isLoading:true});
fetch(`${PATH_BASE}${PATH_SEARCH}?${PARAM_SEARCH}${searchTeam}&${PARAM_PAGE}${page}&${PARAM_HPP}${DEFAULT_HPP}`)
//返回的响应需要被转化成 JSON 格式的数据结构
.then(response => response.json())
.then(result => this.setSearchTopStories(result))
//如果在发起请求时出现错误,这个函数会进入到 catch 中而不是 then 中
.catch(e => this.setState({error:e}));
}
// 获取数据
componentDidMount() {
const { searchTeam } = this.state;
this.setState({searchKey:searchTeam})
this.fetchSearchTopStories(searchTeam);
}
render(){
const { searchTeam, results,searchKey,error,isLoading} = this.state;
const page=(results && results[searchKey]&&results[searchKey].page) || 0;
const list=(results && results[searchKey]&&results[searchKey].hits) || [];
// 条件渲染
// if (!result) { return null; }
// 有错误发生时,获取error,利用条件渲染来显示一个错误信息。
if(error){
return <p>Something went wrong</p>
}
return (
<div className="page">
<div className="interactions">
{/* 用于搜索的输入组件 */}
<Search
value={searchTeam}
onChange={this.onSerachChange}
onSubmit={this.onSerachSubmit}
>
Search
</Search>
</div>
{error?
<div className="interactions">
<p>Something went wrong</p>
</div>
:
<Table
list={list}
onDissmisss={this.onDissmisss}
/>
}
<div className="interactions">
<ButtonWithLoading
isLoading={isLoading}
onClick={() => this.fetchSearchTopStories(searchKey, page + 1)}>
More
</ButtonWithLoading>
</div>
</div>
);
}
}
const Button=({onClick,className='',children,})=>
<button
onClick={onClick}
className={className}
type="button"
>
{children}
</button>
const ButtonWithLoading=withLoading(Button);
//Search 组件
class Search extends Component{
// 使用 this 对象、适当的生命周期方法和 DOM API 在组件挂载的时候来聚焦 input 字段。
componentDidMount(){
if(this.input){
this.input.focus();
}
}
render(){
const {
value,onChange,onSubmit,children}=this.props;
return(
<form onSubmit={onSubmit}>
<input
type="text"
value={value}
onChange={onChange}
ref={(node)=>{this.input=node;}}
/>
<button type="submit">
{children}
</button>
</form>
)
}
}
const Sort=({sortKey,activeSortKey,onSort,children})=>{
const sortClass=['button-inline'];
if(sortKey===activeSortKey){
sortClass.push('button-active');
}
return(
<Button onClick={()=>onSort(sortKey)} className={sortClass.join('')}>
{children}
</Button>
);}
//列表样式
const largeColumn = {
width: '40%',
};
const midColumn = {
width: '30%',
};
const smallColumn = {
width: '10%',
};
class Table extends Component{
constructor(props){
super(props);
// 将状态和有关排序的方法从 App 组件向下移动到 Table 组件中。
this.state={
sortKey:'NONE',
isSortReverse:false,
};
this.onSort=this.onSort.bind(this);
}
onSort(sortKey){
const isSortReverse=this.state.sortKey===sortKey && !this.state.isSortReverse;
this.setState({sortKey,isSortReverse})
}
render(){
const {list,onDissmisss}=this.props;
const {sortKey,isSortReverse}=this.state;
const sortedList=SORTS[sortKey](list);
const reverseSortedList=isSortReverse?sortedList.reverse():sortedList;
return(
<div className="table">
<div className="table-header">
<span style=>
<Sort
sortKey={'TITLE'}
onSort={this.onSort}
activeSortKey={sortKey}
>
Title
</Sort>
</span>
<span style=>
<Sort
sortKey={'AUTHOR'}
onSort={this.onSort}
activeSortKey={sortKey}
>
Author
</Sort>
</span>
<span style=>
<Sort
sortKey={'COMMENTS'}
onSort={this.onSort}
activeSortKey={sortKey}
>
Comments
</Sort>
</span>
<span style=>
<Sort
sortKey={'POINTS'}
onSort={this.onSort}
activeSortKey={sortKey}
>
Points
</Sort>
</span>
<span style=>
Archive
</span>
</div>
{reverseSortedList.map(item=>
<div key={item.objectID} className="table-row">
<span style={midColumn}>
<a href={item.url}>{item.title}</a>
</span>
<span style={smallColumn}>{item.points}</span>
<span style={largeColumn}>{item.author}</span>
<span style={midColumn}>{item.num_comments}</span>
<span style={midColumn}>
<Button onClick={()=>onDissmisss(item.objectID)}
className="button-inline"
>
Dissmiss
</Button>
</span>
</div>
)}
</div>
);
}
}
export default App;