React 05 :: 使用QueryString的Demo

755 阅读2分钟

前言

上一篇文章中,我们介绍了,如何简单地使用react-router-dom。那么如果我们有一个entity-list,如何redirect某一个entity上面呢,传统的web应用,我们很自然的联想到了query-string。当然,react router也不例外,我们也可以使用query-string来实现这个功能。

本文的demo地址:

gitlab.com/yafeya/reac…

1. 目标

在页面上创建两个新的Component, ItemListItemDetail

ItemList:用于显示Item实例的列表,并通过Link组件routeItemDetail Component中。

ItemDetail:用于显示某一个Item的具体信息。

2. ItemList

对于ItemList组件,我们继续使用Redux来添加Item的所有实例。下面是我摘抄的repo中的代码,可以看到,我在MockApi中生成了5个Item的实例。

注:Linkto需要和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>
    ); 
}

5. 运行效果

screen-capture (4).gif