前言
上一篇文章中,我们介绍了,如何简单地使用react-router-dom。那么如果我们有一个entity-list,如何redirect某一个entity上面呢,传统的web应用,我们很自然的联想到了query-string。当然,react router也不例外,我们也可以使用query-string来实现这个功能。
本文的demo地址:
1. 目标
在页面上创建两个新的Component, ItemList与ItemDetail。
ItemList:用于显示Item实例的列表,并通过Link组件route到ItemDetail Component中。
ItemDetail:用于显示某一个Item的具体信息。
2. ItemList
对于ItemList组件,我们继续使用Redux来添加Item的所有实例。下面是我摘抄的repo中的代码,可以看到,我在MockApi中生成了5个Item的实例。
注:
Link的to需要和Router.ts中的routing policy保持一致.
<Link className="operationWrapper btn btn-primary" to={"/item/" + item.id}>Detail</Link>
// ItemList.tsx
interface ItemsProps extends RouteComponentProps {
items: Item[],
fetchItems(): void;
}
export const Items: React.FC<ItemsProps> = (props) => {
useEffect(() => {
props.fetchItems();
});
return (
<div className="App">
<header className="App-header">
<Image src={logo} alt="logo"></Image>
<div className="itemsWrapper">
<div className="itemHeaderWrapper">
<span className="itemTitle">Name</span>
<span className="operationWrapper">Operation</span>
</div>
{
props.items.map((item, index) =>
<div key={item.id} className="itemWrapper" data-index={index % 2}>
<span className="itemTitle">{item.name}</span>
// to 'item/:id', 参见Router代码。
<Link className="operationWrapper btn btn-primary" to={"/item/" + item.id}>Detail</Link>
</div>
)
}
</div>
<Link to="/">Home</Link>
<button className="btn btn-primary" onClick={props.history.goBack}>Back</button>
</header>
</div>
);
}
const mapStateToProps = (state: State) => {
const { items } = state.items;
return { items };
}
const mapDispatchToProps = (dispatch: ThunkDispatch<any, any, AnyAction>) => ({
fetchItems: () => dispatch(fetchItems())
});
export const ItemsWrapper = connect(mapStateToProps, mapDispatchToProps)(Items);
// ItemList.tsx
export interface ItemListState {
items: Item[];
isFetching: boolean;
succeed: boolean;
}
const FETCHING_ITEMS = 'FETCHING_ITEMS';
const FETCHED_ITEMS = 'FETCHED_ITEMS';
export interface FetchingItemsAction {
type: string
};
export interface FetchedItemsAction {
type: string;
payload: Item[];
}
// Mock Api
function getItemsAsync(): Promise<Item[]> {
return new Promise<Item[]>((resolve, reject) => {
resolve([
{ id: 'item1', name: 'item1', description: 'this is item1' },
{ id: 'item2', name: 'item2', description: 'this is item2' },
{ id: 'item3', name: 'item3', description: 'this is item3' },
{ id: 'item4', name: 'item4', description: 'this is item4' },
{ id: 'item5', name: 'item5', description: 'this is item5' },
]);
});
}
//Action Creator
export function fetchItems(): ThunkAction<
// Promise of last dispatched action
Promise<FetchedItemsAction>,
// Data type of the last action
Item[],
// The type of the parameter for the nested function
unknown,
// The type of last action to dispatch.
FetchedItemsAction> {
return async (dispatch: ThunkDispatch<Date, unknown, AnyAction>) => {
let fetchingAction: FetchingItemsAction = { type: FETCHING_ITEMS };
// we can dispatch middle action in the creator.
dispatch(fetchingAction);
let items = await getItemsAsync();
let fetchedItemsAction: FetchedItemsAction = {
type: FETCHED_ITEMS,
// property name must be 'payload'
payload: items
};
return dispatch(fetchedItemsAction);
};
};
const initState: ItemListState = {
items: [],
isFetching: false,
succeed: true
};
export function itemsReducer(state = initState, action: any): ItemListState {
let result = initState;
switch (action.type) {
case FETCHING_ITEMS:
result = {
...state,
isFetching: true
};
break;
case FETCHED_ITEMS:
result = {
...state,
isFetching: false,
succeed: true,
items: action.payload
};
break;
default:
result = state;
break;
}
return result;
}
3. ItemDetail
获取query-string的方式可以参照下面代码,首先需要把query-string声明到RouteParams结构中,然后将RouteParams声明到ItemProps的泛型参数中。
这样,在Component中就可以通过props.match.params.id获取到query-string。
interface RouteParams {
id: string
}
interface ItemProps extends RouteComponentProps<RouteParams> {
items: Item[];
}
export const ItemDetail: React.FC<ItemProps> = (props) => {
const id = props.match.params.id;
const [item, setItem] = useState(props.items.find(x => x.id == id));
return (
<div className="App">
<header className="App-header">
<Image src={logo} alt="logo"></Image>
<div className="itemDetailWrapper">
<div>ID:</div>
<div>{item?.id}</div>
<div>Name:</div>
<div>{item?.name}</div>
<div>Description:</div>
<div>{item?.description}</div>
</div>
<Link to="/">Home</Link>
<button className="btn btn-primary" onClick={props.history.goBack}>Back</button>
</header>
</div>
);
}
const mapStateToProps = (state: State) => {
const { items } = state.items;
return { items };
}
export const ItemDetailWrapper = connect(mapStateToProps, {})(ItemDetail);
4. 修改Router.tsx
export const Router = ()=>{
return (
<BrowserRouter>
<Switch>
<Route exact={true} path="/" component={Home}/>
<Route path="/redux" component={Redux}/>
<Route path="/items" component={ItemsWrapper}/>
// 带query-string的router policy,需要与ItemList中的Link->to中的url格式匹配
<Route path="/item/:id" component={ItemDetailWrapper}/>
<Route component={()=> <Redirect to="/"/>} />
</Switch>
</BrowserRouter>
);
}