摘要:一般情況下,都是作為等其他子路由的上層路由,使用了,接收一個(gè)屬性,傳遞給消費(fèi)子組件。創(chuàng)建對(duì)象,兼容老瀏覽器,其他和沒(méi)有大區(qū)別總結(jié)分為四個(gè)包,分別為,其中是瀏覽器相關(guān),是相關(guān),是核心也是共同部分,是一些配置相關(guān)。
這篇文章主要講的是分析 react-router 源碼,版本是 v5.x,以及 SPA 路由實(shí)現(xiàn)的原理。
文章首發(fā)地址
單頁(yè)面應(yīng)用都用到了路由 router,目前來(lái)看實(shí)現(xiàn)路由有兩種方法 hash 路由和 H5 History API 實(shí)現(xiàn)。
而 react-router 路由,則是用到了 history 庫(kù),該庫(kù)其實(shí)是對(duì) hash 路由、history 路由、memory 路由(客戶端)進(jìn)行了封裝。
下面先看看 hash 和 history 是怎樣實(shí)現(xiàn)路由的。
hash routerhash 是 location 的屬性,在 URL 中是 #后面的部分。如果沒(méi)有#,則返回空字符串。
hash 路由主要實(shí)現(xiàn)原理是:hash 改變時(shí),頁(yè)面不發(fā)生跳轉(zhuǎn),即 window 對(duì)象可以對(duì) hash 改變進(jìn)行監(jiān)聽(tīng)(hashchange 事件),只要 url 中 hash 發(fā)生改變,就會(huì)觸發(fā)回調(diào)。
利用這個(gè)特性,下面模擬實(shí)現(xiàn)一個(gè) hash 路由。
實(shí)現(xiàn)步驟:
初始化一個(gè)類(lèi)
記錄路由、history 歷史值
添加 hashchange 事件,hash 發(fā)生改變時(shí),觸發(fā)回調(diào)
初始添加所有路由
模擬前進(jìn)和回退功能
代碼如下:
hash.html
hash router
hash.js
class Routers { constructor() { this.routes = {} this.currentUrl = "" this.history = []; // 記錄 hash 歷史值 this.currentIndex = this.history.length - 1; // 默認(rèn)指向 history 中最后一個(gè) // 默認(rèn)前進(jìn)、后退 this.isBack = false; this.isForward = false; this.onHashChange = this.onHashChange.bind(this) this.backOff = this.backOff.bind(this) this.forward = this.forward.bind(this) window.addEventListener("load", this.onHashChange, false); window.addEventListener("hashchange", this.onHashChange, false); // hash 變化監(jiān)聽(tīng)事件,support >= IE8 } route(path, callback) { this.routes[path] = callback || function () { } } onHashChange() { // 既不是前進(jìn)和后退,點(diǎn)擊 a 標(biāo)簽時(shí)觸發(fā) if (!this.isBack && !this.isForward) { this.currentUrl = location.hash.slice(1) || "/" this.history.push(this.currentUrl) this.currentIndex++ } this.routes[this.currentUrl]() this.isBack = false this.isForward = false } // 后退功能 backOff() { this.isBack = true this.currentIndex = this.currentIndex <= 0 ? 0 : this.currentIndex - 1 this.currentUrl = this.history[this.currentIndex] location.hash = `#${this.currentUrl}` } // 前進(jìn)功能 forward() { this.isForward = true this.currentIndex = this.currentIndex >= this.history.length - 1 ? this.history.length - 1 : this.currentIndex + 1 this.currentUrl = this.history[this.currentIndex] location.hash = `#${this.currentUrl}` } } // 初始添加所有路由 window.Router = new Routers(); Router.route("/", function () { changeBgColor("yellow"); }); Router.route("/blue", function () { changeBgColor("blue"); }); Router.route("/green", function () { changeBgColor("green"); }); const content = document.querySelector("body"); const buttonBack = document.querySelector("#back"); const buttonForward = document.querySelector("#forward") function changeBgColor(color) { content.style.backgroundColor = color; } // 模擬前進(jìn)和回退 buttonBack.addEventListener("click", Router.backOff, false) buttonForward.addEventListener("click", Router.forward, false)
hash 路由兼容性好,缺點(diǎn)就是實(shí)現(xiàn)一套路由比較復(fù)雜些。
在線預(yù)覽
history routerhistory 是 HTML5 新增的 API,允許操作瀏覽器的曾經(jīng)在標(biāo)簽頁(yè)或者框架里訪問(wèn)的會(huì)話歷史記錄。
history 包含的屬性和方法:
history.state 讀取歷史堆棧中最上面的值
history.length 瀏覽歷史中元素的數(shù)量
window.history.replaceState(obj, title, url) 取代當(dāng)前瀏覽歷史中當(dāng)前記錄
window.history.pushState(obj, title, url) 往瀏覽歷史中添加歷史記錄,不跳轉(zhuǎn)頁(yè)面
window.history.popstate(callback) 對(duì)歷史記錄發(fā)生變化進(jìn)行監(jiān)聽(tīng),state 發(fā)生改變時(shí),觸發(fā)回調(diào)事件
window.history.back() 后退,等價(jià)于瀏覽器返回按鈕
window.history.forward() 前進(jìn),等價(jià)于瀏覽器前進(jìn)按鈕
window.history.go(num) 前進(jìn)或后退幾個(gè)頁(yè)面,num 為負(fù)數(shù)時(shí),后退幾個(gè)頁(yè)面
實(shí)現(xiàn)步驟:
初始化一個(gè)類(lèi)
記錄路由
添加 popstate 事件,state 發(fā)生改變時(shí),觸發(fā)回調(diào)
初始添加所有路由
代碼如下:
history.html
h5 router
history.js
class Routers { constructor() { this.routes = {}; this._bindPopState(); } route(path, callback) { this.routes[path] = callback || function () { }; } go(path) { history.pushState({ path: path }, null, path); this.routes[path] && this.routes[path](); } _bindPopState() { window.addEventListener("popstate", e => { // 監(jiān)聽(tīng) history.state 改變 const path = e.state && e.state.path; this.routes[path] && this.routes[path](); }); } } // 初始時(shí)添加所有路由 window.Router = new Routers(); Router.route("/", function () { changeBgColor("yellow"); }); Router.route("/blue", function () { changeBgColor("blue"); }); Router.route("/green", function () { changeBgColor("green"); }); const content = document.querySelector("body"); const ul = document.querySelector("ul"); function changeBgColor(color) { content.style.backgroundColor = color; } ul.addEventListener("click", e => { if (e.target.tagName === "A") { debugger e.preventDefault(); Router.go(e.target.getAttribute("href")); } });
兼容性:support >= IE10
在線預(yù)覽
react-routerreact-router 分為四個(gè)包,分別為 react-router、react-router-dom、react-router-config、react-router-native,其中 react-router-dom 是瀏覽器相關(guān) API,react-router-native 是 React-Native 相關(guān) API,react-router 是核心也是共同部分 API,react-router-config 是一些配置相關(guān)。
react-router 是 React指定路由,內(nèi)部 API 的實(shí)現(xiàn)也是繼承 React 一些屬性和方法,所以 react-router 內(nèi) API 也是 React 組件。
react-router 還用到了 history 庫(kù),這個(gè)庫(kù)主要是對(duì) hash 路由、history 路由、memory 路由的封裝。
下面我們講到的是 react-router v5.x 版本,用到了 context,context 減少了組件之間顯性傳遞 props,具體用法可以看看官方文檔。
創(chuàng)建 context:
var createNamedContext = function createNamedContext(name) { var context = createContext(); context.displayName = name; return context; }; var context = createNamedContext("Router");Router
定義一個(gè)類(lèi) Router,它繼承 React.Component 屬性和方法,所以 Router 也是 React 組件。
_inheritsLoose(Router, _React$Component); // 等價(jià)于 Router.prototype = Object.create(React.Component)
在 Router 原型對(duì)象上添加 React 生命周期 componentDidMount、componentWillUnmount、render 方法。
一般情況下,Router 都是作為 Route 等其他子路由的上層路由,使用了 context.Provider,接收一個(gè) value 屬性,傳遞 value 給消費(fèi)子組件。
var _proto = Router.prototype; _proto.componentDidMount = function componentDidMount() { // todo }; _proto.componentWillUnmount = function componentWillUnmount(){ // todo }; _proto.render = function render() { return React.createElement(context.Provider, props); };
history 庫(kù)中有個(gè)方法 history.listen(callback(location)) 對(duì) location 進(jìn)行監(jiān)聽(tīng),只要 location 發(fā)生變化了,就會(huì) setState 更新 location,消費(fèi)的子組件也可以拿到更新后的 location,從而渲染相應(yīng)的組件。
核心源碼如下:
var Router = function (_React$Component) { // Router 從 React.Component 原型上的繼承屬性和方法 _inheritsLoose(Router, _React$Component); Router.computeRootMatch = function computeRootMatch(pathname) { return { path: "/", url: "/", params: {}, isExact: pathname === "/" }; }; function Router(props) { // 首先定義一個(gè)類(lèi) Router,也是 React 組件 var _this; _this = _React$Component.call(this, props) || this; // 繼承自身屬性和方法 _this.state = { location: props.history.location }; _this._isMounted = false; _this._pendingLocation = null; if (!props.staticContext) { // 如果不是 staticRouter,則對(duì) location 進(jìn)行監(jiān)聽(tīng) _this.unlisten = props.history.listen((location) => { // 監(jiān)聽(tīng) history.location 變化,如果有變化,則更新 locaiton if (_this._isMounted) { _this.setState({ location: location }); } else { _this._pendingLocation = location; } }); } return _this; } var _proto = Router.prototype; // 組件需要有生命周期,在原型對(duì)象上添加 componentDidMount、componentWillUnmount、render 方法 _proto.componentDidMount = function componentDidMount() { this._isMounted = true; if (this._pendingLocation) { this.setState({ location: this._pendingLocation }); } }; _proto.componentWillUnmount = function componentWillUnmount() { if (this.unlisten) this.unlisten(); // 停止監(jiān)聽(tīng) location }; _proto.render = function render() { // 使用了 React Context 傳遞 history、location、match、staticContext,使得所有子組件都可以獲取這些屬性和方法 // const value = { // history: this.props.history, // location: this.state.location, // match: Router.computeRootMatch(this.state.location.pathname), // staticContext: this.props.staticContext // } // return ( //Route// {this.props.children} // // ) return React.createElement(context.Provider, { children: this.props.children || null, value: { history: this.props.history, location: this.state.location, match: Router.computeRootMatch(this.state.location.pathname), staticContext: this.props.staticContext // staticContext 是 staticRouter 中的 API,不是公用 API } }); }; return Router; }(React.Component);
Route 一般作為 Router 的子組件,主要是匹配 path 和渲染相應(yīng)的組件。
Route 使用了 context.Consumer(消費(fèi)組件),訂閱了 Router 提供的 context,一旦 location 發(fā)生改變,context 也會(huì)改變,判斷當(dāng)前 location.pathname 是否與子組件的 path 是否匹配,如果匹配,則渲染對(duì)應(yīng)組件,否則就不渲染。
Route 因?yàn)橛行?kù)傳遞組件方式不同,所以有多種渲染,部分代碼如下:
const {children, render, component} = this.props let renderEle = null; // 如果有 children,則渲染 children if (children && !isEmptyChildren(children)) renderEle = children // 如果組件傳遞的是 render 及 match 是匹配的,則渲染 render if (render && match) renderEle = render(props) // 如果組件傳遞 component 及 match 是匹配的,則渲染 component if (component && match) renderEle = React.createElement(component, props) return ({renderEle} )
核心源碼如下:
var Route = function (_React$Component) { _inheritsLoose(Route, _React$Component); function Route() { return _React$Component.apply(this, arguments) || this; } var _proto = Route.prototype; _proto.render = function render() { var _this = this; // context.Consumer 每個(gè) Route 組件都可以消費(fèi) Router 中 Provider 提供的 context return React.createElement(context.Consumer, null, function (context$$1) { var location = _this.props.location || context$$1.location; var match = _this.props.computedMatch ? _this.props.computedMatch : _this.props.path ? matchPath(location.pathname, _this.props) : context$$1.match; // 是否匹配當(dāng)前路徑 var props = _extends({}, context$$1, { // 處理用 context 傳遞的還是自己傳遞的 location 和 match location: location, match: match }); var _this$props = _this.props, children = _this$props.children, component = _this$props.component, render = _this$props.render; if (Array.isArray(children) && children.length === 0) { children = null; } // let renderEle = null // 如果有 children,則渲染 children // if (children && !isEmptyChildren(children)) renderEle = children // 如果組件傳遞的是 render 及 match 是匹配的,則渲染 render // if (render && props.match) renderEle = render(props) // 如果組件傳遞 component 及 match 是匹配的,則渲染 component // if (component && props.match) renderEle = React.createElement(component, props) // return ( //Redirect// {renderEle} // // ) return React.createElement(context.Provider, { // Route 內(nèi)定義一個(gè) Provider,給 children 傳遞 props value: props }, children && !isEmptyChildren(children) ? children : props.match ? component ? React.createElement(component, props) : render ? render(props) : null : null); }); }; return Route; }(React.Component);
重定向路由:from 是從哪個(gè)組件來(lái),to 表示要定向到哪里。
根據(jù)有沒(méi)傳屬性 push,有傳則是往 state 堆棧中新增(history.push),否則就是替代(history.replace)當(dāng)前 state。
Redirect 使用了 context.Consumer(消費(fèi)組件),訂閱了 Router 提供的 context,一旦 location 發(fā)生改變,context 也會(huì)改變,則也會(huì)觸發(fā)重定向。
源碼如下:
function Redirect(_ref) { var computedMatch = _ref.computedMatch, to = _ref.to, _ref$push = _ref.push, push = _ref$push === void 0 ? false : _ref$push; return React.createElement(context.Consumer, null, (context$$1) => { // context.Consumer 第三個(gè)參數(shù)是一個(gè)函數(shù) var history = context$$1.history, staticContext = context$$1.staticContext; // method 方法:判斷是否是替換(replace)當(dāng)前的 state,還是往 state 堆棧中添加(push)一個(gè)新的 state var method = push ? history.push : history.replace; // 生成新的 location var location = createLocation(computedMatch ? typeof to === "string" ? generatePath(to, computedMatch.params) : _extends({}, to, { pathname: generatePath(to.pathname, computedMatch.params) }) : to); if (staticContext) { // 當(dāng)渲染一個(gè)靜態(tài)的 context 時(shí)(staticRouter),立即設(shè)置新 location method(location); return null; } // Lifecycle 是一個(gè) return null 的空組件,但定義了 componentDidMount、componentDidUpdate、componentWillUnmount 生命周期 return React.createElement(Lifecycle, { onMount: function onMount() { method(location); }, onUpdate: function onUpdate(self, prevProps) { var prevLocation = createLocation(prevProps.to); // 觸發(fā)更新時(shí),對(duì)比前后 location 是否相等,不相等,則更新 location if (!locationsAreEqual(prevLocation, _extends({}, location, { key: prevLocation.key }))) { method(location); } }, to: to }); }); }Switch
Switch 組件的子組件必須是 Route 或 Redirect 組件。
Switch 使用了 context.Consumer(消費(fèi)組件),訂閱了 Router 提供的 context,一旦 location 發(fā)生改變,context 也會(huì)改變,判斷當(dāng)前 location.pathname 是否與子組件的 path 是否匹配,如果匹配,則渲染對(duì)應(yīng)的子組件,其他都不渲染。
源碼如下:
var Switch = function (_React$Component) { _inheritsLoose(Switch, _React$Component); function Switch() { return _React$Component.apply(this, arguments) || this; } var _proto = Switch.prototype; _proto.render = function render() { var _this = this; return React.createElement(context.Consumer, null, function (context$$1) { var location = _this.props.location || context$$1.location; var element, match; React.Children.forEach(_this.props.children, function (child) { if (match == null && React.isValidElement(child)) { element = child; var path = child.props.path || child.props.from; match = path ? matchPath(location.pathname, _extends({}, child.props, { path: path })) : context$$1.match; } }); return match ? React.cloneElement(element, { location: location, computedMatch: match // 加強(qiáng)版的 match }) : null; }); }; return Switch; }(React.Component);Link
Link 組件作用是跳轉(zhuǎn)到指定某個(gè)路由。Link 實(shí)際是對(duì) 標(biāo)簽進(jìn)行了封裝。
點(diǎn)擊時(shí)會(huì)觸發(fā)以下:
改變 url,但使用了 e.preventDefault(),所以頁(yè)面沒(méi)有發(fā)生跳轉(zhuǎn)。
根據(jù)是否傳遞屬性 replace,有傳就是替代當(dāng)前 state(history.replace),否則是往 state 堆棧中新增(history.push),從而路由發(fā)生了改變。
路由發(fā)生了改變,由于 Router 中有對(duì) location 進(jìn)行監(jiān)聽(tīng),從而通過(guò) context 傳遞給消費(fèi)子組件,匹配 path 是否相同,渲染相應(yīng)的組件。
核心源碼如下:
function LinkAnchor(_ref) { var innerRef = _ref.innerRef, navigate = _ref.navigate, _onClick = _ref.onClick, rest = _objectWithoutPropertiesLoose(_ref, ["innerRef", "navigate", "onClick"]); // 剩余屬性 var target = rest.target; return React.createElement("a", _extends({}, rest, { // a 標(biāo)簽 ref: innerRef , onClick: function onClick(event) { try { if (_onClick) _onClick(event); } catch (ex) { event.preventDefault(); // 使用 e.preventDefault() 防止跳轉(zhuǎn) throw ex; } if (!event.defaultPrevented && // onClick prevented default event.button === 0 && ( // ignore everything but left clicks !target || target === "_self") && // let browser handle "target=_blank" etc. !isModifiedEvent(event) // ignore clicks with modifier keys ) { event.preventDefault(); navigate(); // 改變 location } } })); } function Link(_ref2) { var _ref2$component = _ref2.component, component = _ref2$component === void 0 ? LinkAnchor : _ref2$component, replace = _ref2.replace, to = _ref2.to, // to 跳轉(zhuǎn)鏈接的路徑 rest = _objectWithoutPropertiesLoose(_ref2, ["component", "replace", "to"]); return React.createElement(__RouterContext.Consumer, null, function (context) { var history = context.history; // 根據(jù) to 生成新的 location var location = normalizeToLocation(resolveToLocation(to, context.location), context.location); var href = location ? history.createHref(location) : ""; return React.createElement(component, _extends({}, rest, { href: href, navigate: function navigate() { var location = resolveToLocation(to, context.location); var method = replace ? history.replace : history.push; method(location); // 如果有傳 replace,則是替換掉當(dāng)前 location,否則往 history 堆棧中添加一個(gè) location } })); }); }BrowserRouter
BrowserRouter 用到了 H5 history API,所以可以使用 pushState、replaceState 等方法。
源碼中主要是用到了 history 庫(kù) createBrowserHistory 方法創(chuàng)建了封裝過(guò) history 對(duì)象。
把封裝過(guò)的 history 對(duì)象傳遞給 Router 的 props。
var BrowserRouter = function (_React$Component) { _inheritsLoose(BrowserRouter, _React$Component); // BrowserRouter 繼承 React.Component 屬性和方法 function BrowserRouter() { var _this; for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } _this = _React$Component.call.apply(_React$Component, [this].concat(args)) || this; // 繼承自身屬性和方法 _this.history = createBrowserHistory(_this.props); // 創(chuàng)建 browser history 對(duì)象,是支持 HTML 5 的 history API return _this; } var _proto = BrowserRouter.prototype; _proto.render = function render() { return React.createElement(Router, { // 以 Router 為 element,history 和 children 作為 Router 的 props history: this.history, children: this.props.children }); }; return BrowserRouter; }(React.Component);HashRouter
HashRouter 與 BrowserRouter 的區(qū)別,主要是創(chuàng)建是以 window.location.hash 為對(duì)象,返回一個(gè) history,主要是考慮到兼容性問(wèn)題。
var HashRouter = function (_React$Component) { _inheritsLoose(HashRouter, _React$Component); function HashRouter() { var _this; for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } _this = _React$Component.call.apply(_React$Component, [this].concat(args)) || this; _this.history = createHashHistory(_this.props); // 創(chuàng)建 hash history 對(duì)象,兼容老瀏覽器 hash,其他和 BrowserRouter 沒(méi)有大區(qū)別 return _this; } var _proto = HashRouter.prototype; _proto.render = function render() { return React.createElement(Router, { history: this.history, children: this.props.children }); }; return HashRouter; }(React.Component);總結(jié)
react-router 分為四個(gè)包,分別為 react-router、react-router-dom、react-router-config、react-router-native,其中 react-router-dom 是瀏覽器相關(guān) API,react-router-native 是 React-Native 相關(guān) API,react-router 是核心也是共同部分 API,react-router-config 是一些配置相關(guān)。
react-router 是 React指定路由,內(nèi)部 API 的實(shí)現(xiàn)也是繼承 React 一些屬性和方法,所以說(shuō) react-router 內(nèi) API 也是 React 組件。
react-router 還用到了 history 庫(kù),這個(gè)庫(kù)主要是對(duì) hash 路由、history 路由、memory 路由的封裝。
Router 都是作為 Route 等其他子路由的上層路由,使用了 context.Provider,接收一個(gè) value 屬性,傳遞 value 給消費(fèi)子組件。
history 庫(kù)中有個(gè)方法 history.listen(callback(location)) 對(duì) location 進(jìn)行監(jiān)聽(tīng),點(diǎn)擊某個(gè) Link 組件,改變了 location,只要 location 發(fā)生變化了,通過(guò) context 傳遞改變后的 location,消費(fèi)的子組件拿到更新后的 location,從而渲染相應(yīng)的組件。
參考react-router github
history github
尋找海藍(lán)96【你了解前端路由嗎?】
History MDN
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/105542.html
摘要:通過(guò)前端路由可以實(shí)現(xiàn)單頁(yè)應(yīng)用本文首先從前端路由的原理出發(fā),詳細(xì)介紹了前端路由原理的變遷。接著從的源碼出發(fā),深入理解是如何實(shí)現(xiàn)前端路由的。執(zhí)行上述的賦值后,頁(yè)面的發(fā)生改變。 ??react-router等前端路由的原理大致相同,可以實(shí)現(xiàn)無(wú)刷新的條件下切換顯示不同的頁(yè)面。路由的本質(zhì)就是頁(yè)面的URL發(fā)生改變時(shí),頁(yè)面的顯示結(jié)果可以根據(jù)URL的變化而變化,但是頁(yè)面不會(huì)刷新。通過(guò)前端路由可以實(shí)現(xiàn)...
摘要:相關(guān)配置請(qǐng)參考中文文檔。關(guān)于的更多使用方法及理解需要詳細(xì)具體講解,涉及篇幅較大,本文暫不涉及,有興趣可以看文檔中文文檔,我會(huì)整理后再單獨(dú)章節(jié)分享接下來(lái)我們將編寫(xiě)路由組件這與有一些差別,原來(lái)的方法已經(jīng)不再使用,在中或組件從中引入。 ??????相信很多剛?cè)肟覴eact的小伙伴們有一個(gè)同樣的疑惑,由于React相關(guān)庫(kù)不斷的再進(jìn)行版本迭代,網(wǎng)上很多以前的技術(shù)分享變得不再適用。比如react-...
摘要:更多相關(guān)介紹請(qǐng)看這特點(diǎn)僅僅只是虛擬最大限度減少與的交互類(lèi)似于使用操作單向數(shù)據(jù)流很大程度減少了重復(fù)代碼的使用組件化可組合一個(gè)組件易于和其它組件一起使用,或者嵌套在另一個(gè)組件內(nèi)部。在使用后,就變得很容易維護(hù),而且數(shù)據(jù)流非常清晰,容易解決遇到的。 歡迎移步我的博客閱讀:《React 入門(mén)實(shí)踐》 在寫(xiě)這篇文章之前,我已經(jīng)接觸 React 有大半年了。在初步學(xué)習(xí) React 之后就正式應(yīng)用到項(xiàng)...
摘要:瀏覽器端使用的和集成使用時(shí)會(huì)用到中路由分類(lèi)基于提供的和事件來(lái)保持和的同步。路由剖析在上面的示例中是轉(zhuǎn)發(fā)的樞紐在這個(gè)中轉(zhuǎn)站有很多線路通過(guò)開(kāi)關(guān)可以啟動(dòng)列車(chē)的運(yùn)行乘坐列車(chē)就可以發(fā)現(xiàn)新大陸。 引言 在使用react做復(fù)雜的spa開(kāi)發(fā)中,開(kāi)發(fā)中必不可少的就是react-router,它使用Lerna管理多個(gè)倉(cāng)庫(kù), 在browser端常使用的幾個(gè)如下所示 react-router 提供了路由的...
閱讀 1845·2023-04-26 00:20
閱讀 1899·2021-11-08 13:21
閱讀 2112·2021-09-10 10:51
閱讀 1679·2021-09-10 10:50
閱讀 3367·2019-08-30 15:54
閱讀 2202·2019-08-30 14:22
閱讀 1483·2019-08-29 16:10
閱讀 3152·2019-08-26 11:50