react-router 4.0 初學

React Router 4.0 (以下簡稱 RR4) 已經正式發佈,它遵循React的設計理念,即萬物皆組件。所以 RR4 只是一堆 提供了導航功能的組件(還有若干對象和方法),具有聲明式(引入即用),可組合性的特點。

爲什麼要使用router?

使用的目的是在具有單頁面結構的優勢下,也能通過不同路徑匹配不同頁面,實現多頁面的效果。也就是說一個<route>就是一個單獨的頁面。

本文檔內使用的是 react-router 4.2

安裝

只需安裝react-router-dom

npm install react-router-dom --save-dev

組件

總的樣例代碼:
import { BrowserRouter, Route } from 'react-router-dom'

const PrimaryLayout = () => (
  <div className="primary-layout">
    <header>
      Our React Router 4 App
    </header>
    <main>
      <Route path="/" exact component={HomePage} />
      <Route path="/users" component={UsersPage} />
    </main>
  </div>
)

const HomePage =() => <div>Home Page</div>
const UsersPage = () => <div>Users Page</div>

const App = () => (
  <BrowserRouter>
    <PrimaryLayout />
  </BrowserRouter>
)

render(<App />, document.getElementById('root'))

<BrowserRouter>

由於我們的應用程序是用於瀏覽器的,所以我們需要將最外層組件(例子中的App)封裝在來自 v4BrowserRouter 中。

<Route>

他的作用就是當url中的地中與組件上的path相匹配的時候,渲染該組件內的UI界面。

與之前版本不同的是,之前使用{props.children}來嵌套組件,現在直接使用<Route>,而且<Route>在哪裏編寫,如果路由匹配,子組件就在哪裏渲染。

該組件自帶3個render method屬性:
1. component:後面跟組件名稱,即當路徑匹配的時候渲染該component組件;
2. render:後面跟一個函數,進行內聯渲染,即當路徑匹配時,執行該函數並渲染其中的UI;
3. children:後面跟函數,用的較少。

path屬性

path屬性規定了什麼時候渲染該route組件中的組件。

exact屬性

規定了當路由完全匹配path中的路由時,渲染相應組件。

<Switch>

之前的路由匹配都是排他性匹配,意味着一次只能匹配到一條路由;V4的路由匹配時包容性匹配,也就是說,多個<Route>可以同時匹配和渲染。

例子中,我們試圖根據路徑渲染 HomePage 或者 sersPage。如果從示例中刪除了 exact 屬性,那麼在瀏覽器中訪問 /users 時,HomePageUsersPage 組件將同時被渲染。

在V4中實現排他性匹配非常簡單,只要使用<Switch>組件就可以了。使用後,<Switch>內部的各子組件一次只能匹配到一個路由。

如果各個子組件中均沒有exact屬性,則匹配哪個子組件與其位置是有關的。例如下面這個例子:

<Switch>
    <Route path="/" exact component={HomePage} />
    <Route path="/users/add" component={UserAddPage} />
    <Route path="/users" component={UsersPage} />
    <Redirect to="/" />
</Switch>

我們在 /users 之前策略性地放置了 /users/add 的路由,以確保正確匹配。由於路徑 /users/add 將匹配 /users/users/add,所以最好先把 /users/add 放在前面。

當然,如果我們以某種方式使用 exact,我們可以把它們放在任何順序上。

<Redirect>

這個組件就相當於一個跳轉,將當前地址導航到一個新的地址。在例子中的<Switch>中只有在其他路由不匹配的情況下,纔會渲染重定向組件。

作用就相當於一個a標籤,將頁面跳轉至指定鏈接。
例如:

<Link to="/courses" />

除了直接寫字符串,to後接一個對象還能攜帶參數跳轉到指定路徑:

<Link to={{
  pathname: '/course',
  search: '?sort=name',
  state: { price: 18 }
}} />

這是 <Link>的特殊版,顧名思義這就是爲頁面導航準備的。因爲導航需要有 “激活狀態”。


路由的嵌套

路由嵌套實現的方式有很多種,比較簡單的就是把子路由和其父路由並排寫在一起,如下所示:

<main>
    <Switch>
        <Route path="/" exact component={HomePage} />
        <Route path="/users" exact component={BrowseUsersPage} />
        <Route path="/users/:userId" component={UserProfilePage} />
        <Route path="/products" exact component={BrowseProductsPage} />
        <Route path="/products/:productId" component={ProductProfilePage} />
        <Redirect to="/" />
    </Switch>
</main>

上面的這種寫法不是很好,首先父路由的<Route>必須要加上exact屬性才能進行匹配渲染相應的組件;其次,子組件的的佈局應該是一致的(比如userId爲1和2的組件佈局是一樣的),其中相同的內容會在子組件每一次生成的時候重新請求、渲染,造成了代碼的重複,也會使網絡流量造成不必要的浪費。

還有一種比較好的寫法是將子組件單拿出來,父組件包容性匹配路徑,再到子組件中在具體匹配,代碼如下所示:

<main>
    <Switch>
        <Route path="/" exact component={HomePage} />
        <Route path="/users" component={UserSubLayout} />        //沒有exact屬性
        <Route path="/products" component={ProductSubLayout} />     //沒有exact屬性
        <Redirect to="/" />
    </Switch>
</main>

//嵌套組件
const UserSubLayout = () => (
  <div className="user-sub-layout">
    <aside>
      <UserNav />
    </aside>
    <div className="primary-content">
      <Switch>
        <Route path="/users" exact component={BrowseUsersPage} />
        <Route path="/users/:userId" component={UserProfilePage} />
      </Switch>
    </div>
  </div>
)

在上面代碼中,父級路由寫在了一起,路徑中沒有exact屬性,做包容性匹配,而相應的子路由被單獨拿了出來,寫在了父級路由將要渲染的組件裏,進行再次的路由匹配。

有一點需要注意的是,即使我們在佈局結構中深入嵌套,路由仍然需要識別它們的完整路徑才能匹配。爲了節省重複輸入,改用 props.match.path來代替path中的字符串路徑:

const UserSubLayout = props => (
  <div className="user-sub-layout">
    <aside>
      <UserNav />
    </aside>
    <div className="primary-content">
      <Switch>
        <Route path={props.match.path} exact component={BrowseUsersPage} />
        <Route path={`${props.match.path}/:userId`} component={UserProfilePage} />
      </Switch>
    </div>
  </div>
)

完整代碼

import React from 'react';
import ReactDOM from 'react-dom';
import {BrowserRouter,HashRouter, Route, NavLink, Switch, Link} from 'react-router-dom';

const PageLayout = ()=> (
    <div>
        <HeaderLayout />
        <div>
            <Route path='/' exact component={Home} />
            <Route path='/schedule' component={Schedule} />
            <Route path='/player' component={Player} />
        </div>
    </div>
)

const HeaderLayout = () => (
    <div className='header'>
        <NavLink to='/' exact activeClassName="active" className>首頁</NavLink>
        <NavLink to='/schedule' activeClassName="active" className>賽程</NavLink>
        <NavLink to='/player' activeClassName="active" className>球員表</NavLink>
    </div>
)

const Home =() => (
    <h1>這裏是首頁</h1>
)

const Schedule = () => (
    <ul>
        <li>one</li>
        <li>two</li>
        <li>three</li>
        <li>four</li>
    </ul>
)

const playerName = ['lbj','kb','kg','dw'];

const Player = ({match}) => (
    <div>
        <TopHeader member = {playerName} />
        <div>
            <Switch>
                <Route path={match.path} exact render={() => <h1>這裏是Player首頁</h1> }/>
                <Route path={`${match.path}/:member`} component={PlayerProfile}/>
            </Switch>
        </div>
    </div>
)

const PlayerProfile = ({match}) => (
    <h1 userid = {match.params.member}>this is {match.params.member}'s Subpage page!</h1>
)

class TopHeader extends React.Component{
    constructor(props){
        super(props);
        this.handleClick = this.handleClick.bind(this);
    }
    handleClick(event){
        console.log(event.target.innerText);
        console.log(location);
        location.href += `/${event.target.innerText}`;
    }
    render(){
        return(
            <div>
                <ul>
                    {this.props.member.map((item,key)=>(
                        // <p key = {key} onClick={this.handleClick}>{item}</p>
                        <li key = {key}><Link to={`/player/${item}`}>{item}</Link></li>
                    ))}
                </ul>
            </div>
        )
    }
}

const App = () => (
    <HashRouter>
        <PageLayout />
    </HashRouter>
)

ReactDOM.render(<App />,document.getElementById('root'));

注意:本例中使用的是HashRouter,之前使用的是BrowserRouter,由於其存在不能直接進入子路由,而且不能在子路由下刷新頁面,才換成的HashRouter

換上<BrowserRouter>之後會出現 2 個問題:

如果你不是通過服務器啓動應用,因爲chrome自身的安全機制,在本地環境下根本不能用chrome玩。這個不關鍵,我本地測試換個瀏覽器還不行麼,本地起個服務器也不麻煩。

關鍵問題,刷新就是404。原因很簡單,BrowserRouter 和 HashRouter 完全不同,前者利用H5的 history 接口,前臺路由就是後臺收到的路由,你點到其他頁面一刷新,得,根本沒這個文件,服務器也很無辜,到底讓我渲染個啥?後臺也可以簡單的解決這個問題:甭管你請求的啥地址,我先把入口文件扔給你。node處理方式如下(需要express):

app.get('*',(request,response)=>{
  response.sendFile(path.resolve(__dirname,'../index.html'))
})
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章