react-router理解

react-router理解

1. Provider和Consumer

Provider和Comsumer是React提供的兩個原生組件,Provider的value屬性傳遞數據,Provider包裹內的所有Consumer都可以直接獲取到Provider的數據

獲取方法

let { Provider, Consumer } = React.createContext();

使用方法

<Provider value={{ name: "aeipyuan" }}>
    <div>
        <div>
            <Consumer>{state => {
                console.log(state)
                return <div>{state.name}</div>
            }}</Consumer>
        </div>
    </div>
</Provider>

2. HashRouter 和 Route

根據hash改變組件的步驟

  • HashRouter綁定hashchange事件,每次發生都會觸發setState,並記錄新的hash
  • 數據改變觸發render函數,render一個Provider,並且將新數據(pathname)放到Provider的value屬性上
  • Router作爲Router的子組件,也會重新執行render,此時通過Consumer接受Provider提供的數據,並且比對Route自身傳遞的path屬性和state裏面的pathname,如果符合條件則返回Route屬性傳入的Component進行渲染,否則返回null不渲染
/* index.jsx */
<Router>
    <Route path="/home" component={Home}></Route>
    <Route path="/profile" component={Profile}></Route>
    <Route path="/user" component={User}></Route>
</Router>
/* HashRouter.js */
export default class HashRouter extends React.Component {
    constructor() {
        super();
        this.state = {
            location: {/* slice去除# */
                pathname: window.location.hash.slice(1) || '/'
            }
        }
    }
    /* 掛載組件完成時綁定hashchange事件 */
    componentDidMount() {
        window.location.hash = window.location.hash || '/'
        window.addEventListener('hashchange', () => {
            this.setState({
                location: {
                    ...this.state.location,
                    pathname: window.location.hash.slice(1) || '/'
                }
            })
        })
    }
    render() {
        /* 要傳給Route使用的值 */
        let value = {
            location: this.state.location
        }
        return (<Provider value={{ ...value }}>
            {this.props.children}
        </Provider>)
    }
}
/* Route.js */
export default class Route extends React.Component {
    render() {
        return (<Consumer>
            {state => {
                /* 瀏覽器路徑 */
                let pathname = state.location.pathname;/* /home */
                /* 獲取Route的路徑和組件,以及是否精確匹配 */
                //{path: "/home", component: ƒ, exac:true}
                let { path, component: Component, exac = false } = this.props;
                /* 比對路徑 end爲true時爲嚴格模式時*/
                let reg = pathToRegexp(path, [], { end: exac });
                /* 通過屬性把Provider的數據繼續傳給子組件 */
                return reg.test(pathname) ? <Component {...state}/> : null;
            }}
        </Consumer>)
    }
}

3. Link

/* index.jsx */
<div>
    <Link to="/home"> 主頁 </Link>
    <Link to="/profile"> 個人中心 </Link>
    <Link to="/user"> 用戶 </Link>
</div>

實現步驟

  • 擴充HashRender通過Provider傳遞的方法,增加push方法強制改變hash
/* 要傳給Consumer使用的數據 */
let value = {
    location: this.state.location,
    history: {
        push(to) {
            window.location.hash = to;
        }
    }
}
  • Link組件插入一個Consumer,返回值是一個a標籤,點擊觸發HashRender提供的push方法
return (<Consumer>
    {state => {
        /* 調用state的push函數,強制跳轉 */
        return <a onClick={() => {
            state.history.push(this.props.to)
        }}>{this.props.children}</a>
    }}
</Consumer>)

4. Switch

<Switch>
    <Route path="/home" component={Home}></Route>
    <Route path="/home" component={Home}></Route>
    <Route path="/profile" component={Profile}></Route>
    <Route path="/user" component={User}></Route>
</Switch>

Switch實現每次只匹配一個組件的效果

  • 利用Consumer獲取瀏覽器hash值
  • 遍歷孩子,遇到符合條件的直接停止遍歷,不處理後面的子元素
return (<Consumer>
    {state => {
        let { pathname } = state.location;
        /* 遍歷子元素,找出符合條件的進行渲染然後返回 */
        for (let child of this.props.children) {
            /* 比對 */
            let path = child.props.path || '';
            let exac = child.props.exac || false;
            let reg = pathToRegexp(path, [], { end: exac });
            if (reg.test(pathname)) return child;
        }
        return null;
    }}
</Consumer>)

5. Redirect

<Switch>
    <Route path="/home" component={Home}></Route>
    <Route path="/profile" component={Profile}></Route>
    <Route path="/user" component={User}></Route>
    <Redirect to="/err"></Redirect>
</Switch>

當所有頁面都不匹配時強制跳轉

return (<Consumer>
    {state => {
        // 直接強行改變hash
        state.history.push(this.props.to);
        return null;
    }}
</Consumer>)

6. 根據參數匹配頁面

match的作用是使組件可以通過類似/home/:id的方式傳遞數據,渲染出id相關數據

/* user.jsx */
return (<div>
    <div>
        <Link to="/user/add"> 用戶添加 </Link>
        <Link to="/user/list"> 用戶列表 </Link>
        <hr />
    </div>
    <div>
        <Route path="/user/add" component={userAdd}></Route>
        <Route path="/user/list" component={userList}></Route>
        <Route path="/user/detail/:id/:name" component={userDetail}></Route>
    </div>
</div>)
/* userList.jsx */
return (<div>
        <Link to="/user/detail/1/Amy">  Amy  </Link>
        <Link to="/user/detail/2/Mike">  Mike  </Link>
        <Link to="/user/detail/3/Nancy">  Nancy  </Link>
</div>)
/* userDetail.js */
return (
    <div>
        Detail
        <div>id: {this.props.match.params.id}</div>
        <div>name:{this.props.match.params.name}</div>
    </div>
)

實現步驟:

  • 利用pathToRegexp函數解析對比可以得到key和values的映射關係
let { pathToRegexp } = require('path-to-regexp')
let keys = [];
let reg = pathToRegexp('/user/detail/:id/:name', keys, { end: false })
console.log(keys.map(v => v.name));//[ 'id', 'name' ]
/* 測試 */
let pathname = '/user/detail/111/bbb';
let [url, ...values] = path.match(reg);
console.log(values);//['111','bbb'];對應於['id','name']
  • 依據pathToRegexp規則修改Route類的render函數,將映射應用到params上,傳遞給子元素
/* Route.js */
render() {
    return (<Consumer>
        {state => {
            /* 瀏覽器路徑 */
            let { pathname } = state.location;/* /home */
            /* 獲取Route的路徑和組件,以及是否精確匹配 */
            //{path: "/home", component: ƒ, exac:true}
            let { path, component: Component, exac = false } = this.props;
            /* 比對路徑 end爲true時爲嚴格模式時*/
            let keys = [];
            let reg = pathToRegexp(path, keys, { end: exac });
            keys = keys.map(v => v.name);
            let [url, ...values] = pathname.match(reg) || [];
            // console.log(keys)//['id','name']
            // console.log(values);//['1','Amy']
            /* 設置傳給子節點的數據 */
            let props = {
                ...state,
                match: {
                    params: keys.reduce((data, item, idx) => {
                        data[item] = values[idx];
                        return data;
                    }, {})//結果 {id:'1',name:'Amy'}
                }
            }
            return reg.test(pathname) ? <Component {...props} /> : null;
        }}
    </Consumer>)
}
過程描述
  1. 假設to="/user/detail/1/Amy",點擊link標籤,調用state傳入的state.history.push(to)方法,改變window.location.hash
  2. 觸發HashRouter的hashchange事件,改變HashRouter的state重新render包含的內容
  3. App組件的Route匹配到/user渲染User組件
  4. User組件的Route匹配到/user/detail/:id/:name,將{id:'1',name:'Amy'}放到props.history.match,再將整個props解構傳給userDetail

7. 總結

  • HashRouter監聽hash的改變並提供操作hash的方法,每次改變都會觸發包裹的內部元素進行render,並使用Provider傳遞數據
  • Route、Link、Switch、Redirect都是使用Consumer接受HashRouter關於hash的數據和方法,利用這些數據與屬性傳入數據進行比對,從而確定是否渲染組件或者子組件
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章