react-router v4 源碼分析

2022-02-03 Qunar技術沙龍

趙聰

個人介紹:趙聰,2017 年 11 月加入去哪兒網技術團隊。目前在大住宿事業部/大前端/國際業務組,參與開發了 EB 系統、大住宿任務調度系統、國際酒店 Touch 端等項目。個人對前端及 Node.js 相關技術有濃厚興趣。

在最近接的一些新項目中都有用到 react-router,每次都是照著老工程抄過來,碰到問題也都是試來試去浪費過多的時間,因此有必要了解一下其工作原理來提高工作效率。

1 閱讀前注意

React16.3 對 Context 已經正式支持,並提供了全新的API(https://reactjs.org/docs/context.html),react-router 從 18 年 1 月 29 日開始了 Context 相關代碼的逐步升級(https://github.com/ReactTraining/react-router/pull/5908),安裝時可通過 npm install react-router@next 獲取到最新非正式版。

本文中所引用的代碼來自 react-router@4.3.1 目前最新的正式版本,使用的還是老的實驗版 Context API,與 next 版可能存在些許不同,請注意。

2 什麼是 react-router

react-router v4 是一個使用純 react 實現的路由解決方案,可以理解為是對 history 庫的 react 封裝,v4 與其之前的版本在設計方式和理念上有較大的差別,為重寫庫。

react-router 包含有兩個具體實現,react-router-dom 和 react-router-native,他們在核心庫的基礎上提供了自己平臺的專屬組件和函數。因日常開發主要圍繞 touch 端展開,所以本次源碼分析內容以 react-router-dom 為主。

2.1 history

在開始源碼分析前,需要先了解一些基本概念。

history 這個概念來自瀏覽器的 history(歷史記錄),可以對用戶所訪問的頁面按時間順序進行記錄和保存. 這就不得不提一下 history 庫,history 庫借鑑了瀏覽器 history 的概念,對其進行封裝或實現,使得開發者可以在任何js運行環境中實現歷史會話操作,react-router 使用 history 庫對其路由狀態進行監聽和管理,使得他能在非瀏覽器環境下運行。

history 庫提供了三種路由的實現方式,browser history,hash history 和 memory history,無論使用的是哪一種,其所創建出來的 history 對象都包含以下屬性和方法。

{

   length, // 歷史堆棧高度

   action, // 當前導航動作有pushpopreplace三種

   location: {

     pathname, // 當前url

     search, // queryString

     hash, // url hash

   },

   push(path[state]), // 將一個新的歷史推入堆棧 (可以理解為正常跳轉)

   replace(path[state]), // 替換當前棧區的內容 (可以理解為重定向)

   go(number), // 移動堆棧指針

   goBack(number), // 返回上一歷史堆棧指針 -1

   goForward(number), // 前進到下一歷史堆棧指針 +1

   block(string | (location, action) => {}) // 監聽並阻止路由變化

 }

3 構成

以下為 react-router 和 react-router-dom 的項目結構對比:

react-router               react-router-dom

├── README.md              ├── README.md

├── modules                ├── modules

│   ├── MemoryRouter.js    │   ├── BrowserRouter.js

│   ├── Prompt.js          │   ├── HashRouter.js

│   ├── Redirect.js        │   ├── Link.js

│   ├── Route.js           │   ├── MemoryRouter.js

│   ├── Router.js          │   ├── NavLink.js

│   ├── StaticRouter.js    │   ├── Prompt.js

│   ├── Switch.js          │   ├── Redirect.js

│   ├── generatePath.js    │   ├── Route.js

│   ├── index.js           │   ├── Router.js

│   ├── matchPath.js       │   ├── StaticRouter.js

│   └── withRouter.js      │   ├── Switch.js

├── package-lock.json      │   ├── generatePath.j

├── package.json           │   ├── index.js

├── rollup.config.js       │   ├── matchPath.js

└── tools                  │   └── withRouter.js

   ├── babel-preset.js    ├── package-lock.json

   └── build.js           ├── package.json

                          ├── rollup.config.js

                          └── tools

                              ├── babel-preset.js

                              └── build.js

可以發現有很多相同的組件,事實上 react-router-dom 中的同名組件就是從 react-router 核心庫中 re-export 的,所以可以從這些共用的核心組件開始分析。

4 <Router />

Router 組件是所有路由組件的父級組件,為子組件提供當前路由狀態並監聽路由改變並觸發重新渲染。

4.1 props

Router 組件接受一個必要屬性 history,不同的平臺有自己的 history 實現。

4.2 源碼分析

Router 組件設置了一個 context,以供所有子組件能獲取到路由狀態,可以看到設置 context.router 的時候繼承了 this.context.router 的所有屬性,具體原因在這裡(https://github.com/ReactTraining/react-router/issues/4650),其實是因為與其他第三方庫的 context 重名了。

getChildContext() {

   return {

     router: {

       ...this.context.router,

       history: this.props.history, // history 庫生成的 history 對象

       route: {

         location: this.props.history.location,

         match: this.state.match

       }

     }

   };

 }

組件擁有一個名為 match 的 state,用來表示當前路由是否匹配(match),match 由 computeMatch() 函數計算得出,這個函數在 Route 組件中也會出現,作用相同,Router 中為默認值,設置其默認值的原因會在後文講到。

state = {

   match: this.computeMatch(this.props.history.location.pathname)

 };

 computeMatch(pathname) {

   return {

     path: "/",

     url: "/",

     params: {},

     isExact: pathname === "/"

   };

 }

傳入的 history,在生命周期函數 componentWillMount() 中設置了監聽,當路由發生變化的時候重新設置 match 狀態,因 react 的運行機制,父組件的 state 發生改變時,如果設置了 context,會調用 getChildContext() 重新計算 context,並重新渲染。

將監聽放置在 componentWillMount() 中是為了適配伺服器渲染,因為 componentWillMount() 會在伺服器端執行而 componentDidMount() 不會,所以 Redirect 組件在重定向時所改變的狀態會在伺服器端渲染時得到響應,如源碼中的注釋所說。有關 Redirect 組件的內容將會在後文提到。

componentWillMount() {

   const { children, history } = this.props;

   // Do this here so we can setState when a <Redirect> changes the

   // location in componentWillMount. This happens e.g. when doing

   // server rendering using a <StaticRouter>.

   this.unlisten = history.listen(() => {

     this.setState({

       match: this.computeMatch(history.location.pathname)

     });

   });

 }

 componentWillUnmount() {

   this.unlisten();

 }

Router 組件只允許其擁有一個子元素,具體原因(https://github.com/ReactTraining/react-router/issues/5706),以下為中文解釋:

Router 組件經常會被作為頂級組件放到 ReactDOM.render() 裡,像是這樣:

ReactDOM.render(

 <Router>

   <div />

   <div />

 </Router>

)

但是其實 Router 並沒有創建任何 DOM 節點,所以等價於這樣:

ReactDOM.render(

 <div />

 <div />

)

這種寫法是不被 React 允許的。

5 <Route />

路由組件,設置並根據當前路由來判斷是否渲染內容。

5.1 props

path :路由匹配參數;

exact strict sencitive :path 的三種匹配模式;

component render children :Route 組件提供的三種子組件渲染方式,具體區別會在後文提到。

5.2 源碼分析

當初始化或是路由發生改變的時候,會調用 computedMatch() 方法來計算設置的 path 是否匹配當前路由。

由上文可知,當路由狀態改變時,context 會被重新計算. 此時會造成子組件的重新渲染。與 props 類似,context 在改變時,生命周期函數 componentWillReceiveProps() 也會被觸發,使得其 state.match 被重新計算。

與 Router 組件相同,this.state.match 依靠 this.computeMatch() 方法重新計算當前 url 是否匹配當前 Route 設置的路由。

Route 組件如果被 Switch 組件包裹,Switch 組件會為其計算好 match 信息並通過屬性的形式傳入,所以 this.computeMatch() 在第一步會判斷是否存在 computedMatch 屬性以免重複計算,有關 computedMatch 的計算方式會在後文組件源碼分析部分提到。

在默認情況下,Route 組件會選取當前 history location 與 path 做匹配,但也同時支持使用自定義 location,官方文檔中提供了一個使用過渡動畫的例子(https://reacttraining.com/react-router/web/example/animated-transitions)來描述該應用場景。

computeMatch(

 { computedMatch, location, path, strict, exact, sensitive },

 router

) {

 if (computedMatch) return computedMatch; // <Switch> already computed the match for us

 const { route } = router;

 // 如果設置了location屬性優先使用

 const pathname = (location || route.location).pathname;

 return matchPath(pathname, { path, strict, exact, sensitive }, route.match);

}

state = {

 match: this.computeMatch(this.props, this.context.router)

};

componentWillReceiveProps(nextProps, nextContext) {

 this.setState({

   match: this.computeMatch(nextProps, nextContext.router)

 });

}

5.2.1 matchPath()

computeMath() 在對數據簡單轉換後,會調用 matchPath.js 文件中的 matchPath() 方法進行路由匹配計算。

const matchPath = (pathname, options = {}, parent) => {

 if (typeof options === "string") options = { path: options };

 const { path, exact = false, strict = false, sensitive = false } = options;

 // 當沒有path參數的時候採用context.router也就是父級元素的路由信息

 if (path == null) return parent;

 const { re, keys } = compilePath(path, { end: exact, strict, sensitive });

 const match = re.exec(pathname);

 if (!match) return null;

 const [url, ...values] = match;

 const isExact = pathname === url;

 if (exact && !isExact) return null;

 // 匹配成功

 return {

   path,

   url: path === "/" && url === "" ? "/" : url, // 待匹配url也就是當前pathname

   isExact, // 是否完全匹配

   params: keys.reduce((memo, key, index) => {

     memo[key.name] = values[index];

     return memo;

   }, {})

 };

};

compilePath() 方法調用 path-to-regexp 庫,將 path 轉換為正則表達式方便匹配,並根據匹配模式建立緩存。

const patternCache = {};

const cacheLimit = 10000;

let cacheCount = 0;

const compilePath = (pattern, options) => {

// 根據正則的生成條件分類建立緩存

 const cacheKey = `${options.end}${options.strict}${options.sensitive}`;

 const cache = patternCache[cacheKey] || (patternCache[cacheKey] = {});

// 如果當前生成條件下已存在生成好的匹配用正則則直接使用不再耗時重複生成

 if (cache[pattern]) return cache[pattern];

// 正則計算過程

 const keys = []; // 用於儲存在path中匹配出來的key

 const re = pathToRegexp(pattern, keys, options);

 const compiledPattern = { re, keys }; // 返回正則和匹配出來的路由參數

// 如果緩存數量到達上限(10000)則之後的新正則都不再緩存重新生成

 if (cacheCount < cacheLimit) {

   cache[pattern] = compiledPattern;

   cacheCount++;

 }

 return compiledPattern;

};

生成的緩存為如下結構(舉例):

{

   falsefalsefalse: {

     '/routeOne': { re, keys },

     '/routeThree': { re, keys },

     '/routeTwo': { re, keys }

   },

   truefalsefalse: {

     '/': { re, keys },

   },

   ...

}

5.2.2 關於 path-to-regexp 庫

pathToRegExp 提供了四種不同的正則生成方式:

sensitive:大小寫敏感模式,如 /api 和 /Api 不匹配。

strict:嚴格模式,在確切匹配的基礎上,區分 path 結尾的分隔符,如:/api 和 /api/ 不匹配。

end:匹配到尾模式:匹配到 path 字符串結尾,默認為 true 如當 start 為默認值時:/api 和 /api/userName 不匹配。

start:從頭匹配模式:從 path 字符串的頭部開始匹配,默認為 true。

react-router 選用了其中前三種匹配方式,並將 end 更名為 exact。

匹配正則的具體使用方法舉例如下:

var keys = []

// 生成正則

var re = pathToRegexp('/foo/:bar', keys)

// re = /^\/foo\/([^\/]+?)\/?$/i

// keys = [{ name: 'bar', prefix: '/', delimiter: '/', optional: false, repeat: false, pattern: '[^\\/]+?' }]

// 使用正則

var match = re.exec('/foo/aaa');

// match = ['/foo/aaa', 'aaa']

正則被調用後,從返回數組的 1 號元素開始是匹配出的參數的值,computeMath() 更進一步,將其拼接為 key: value 的形式,掛載在返回值的 params 屬性下。

5.2.3 子元素渲染方式

Route 組件支持三種子元素渲染方式,component render children 三個屬性,如果存在,則優先匹配,並且都傳入 { match, location, history, staticContext } 路由信息。

render() {

 const { match } = this.state;

 const { children, component, render } = this.props;

 const { history, route, staticContext } = this.context.router;

 const location = this.props.location || route.location;

 const props = { match, location, history, staticContext };

 // 如果存在則優先匹配

 // 傳入類型為ReactElement

 if (component) return match ? React.createElement(component, props) : null;

 // 傳入類型

 if (render) return match ? render(props) : null;

 if (typeof children === "function") return children(props);

 if (children && !isEmptyChildren(children))

   return React.Children.only(children);

 return null;

}

component 和 render 屬性會根據是否匹配 path 來判斷是否渲染。children 則較為特殊,無論當前路由是否匹配,都會渲染傳入的內容,較為自由,讓開發者自己判斷要顯示的內容,react-router-dom 中的 NavLink 組件就是一個很好的應用例子。

6 <Switch />

功能十分簡單,只渲染匹配成功的第一個路由組件。

包裹 Route 組件,同樣調用 matchPath() 方法,代理 Route 組件計算是否 match,因源碼十分簡單,不做過多解析。

render() {

   const { route } = this.context.router;

   const { children } = this.props;

   const location = this.props.location || route.location;

   let matchchild;

   React.Children.forEach(children, element => {

     // match一旦被賦值說明已出現匹配成功的Route組件後面的直接跳過

     if (match == null && React.isValidElement(element)) {

       const {

         path: pathProp,

         exact,

         strict,

         sensitive,

         from // Redirect的屬性後面會提到

       } = element.props;

       const path = pathProp || from;

       child = element;

       // 計算是否匹配

       match = matchPath(

         location.pathname,

         { path, exact, strict, sensitive },

         route.match

       );

     }

   });

   return match

     // 使用cloneElement為child添加新屬性

     ? React.cloneElement(child, { location, computedMatch: match })

     : null;

 }

7 <Redirect />

重定向組件,相當於 history 的 push 或是 replace 方法的組件化封裝。

7.1 props

當屬性 push 為 true 時,路由切換方式改為跳轉而非重定向。

7.2 源碼分析

調用 isStatic() 方法可以得知當前是否處在伺服器渲染模式下,如在在此模式下,便在 componentWillMount 生命周期函數執行時進行路由跳轉,否則在 componentDidMount 時跳轉。

isStatic() {

   // 只有伺服器渲染模式下 staticContext 有值

   return this.context.router && this.context.router.staticContext;

 }

 componentWillMount() {

   if (this.isStatic()) this.perform();

 }

 componentDidMount() {

   if (!this.isStatic()) this.perform();

 }

當出現如下情況時(https://github.com/ReactTraining/react-router/issues/5003),需要組件更新後重新判斷是否跳轉:

某一 Route 組件匹配成功並開始渲染內容,其子組件中包含一個 Redirect 組件並開始執行重定向,但由於重定向地址和當前地址相同,所以只是重新渲染,這時即使再更新子組件的狀態也不會重定向了,除非重新創建 Redirect 組件。

以下代碼便是為這種情況服務的:

componentDidUpdate(prevProps) {

   const prevTo = createLocation(prevProps.to);

   const nextTo = createLocation(this.props.to);

   if (locationsAreEqual(prevTo, nextTo)) {

     warning(

       false,

       `You tried to redirect to the same route you're currently on: ` +

         `"${nextTo.pathname}${nextTo.search}"`

     );

     return;

   }

   this.perform();

 }

perform() 用來進行重定向操作,其調用 computeTo() 來計算重定向的 url,跳轉的時候分為兩種情況,如果有 computedMatch 的話說明有需要傳遞的參數,則在計算後返回 url,否則直接返回。

例如:當前 location.pathname="/user/123",所以某 path="/user/:id" 的 Route 組件匹配成功,其包含一個 Redirect 子組件 to="/id/:id",則在重定向後地址為 "/id/123"。

具體應用場景:

<Switch>

 <Redirect from='/users/:id' to='/users/profile/:id'/>

 <Route path='/users/profile/:id' component={Profile}/>

</Switch>

computeTo({ computedMatch, to }) {

   if (computedMatch) {

     if (typeof to === "string") {

       return generatePath(to, computedMatch.params);

     } else {

       return {

         ...to,

         pathname: generatePath(to.pathname, computedMatch.params)

       };

     }

   }

   return to;

 }

7.2.1 generatePath()

用於生成跳轉用 url 的方法,與 matchPath 方法類似,是其逆操作,同樣為了提高響應速度使用了緩存。

核心邏輯為調用 path-to-regexp 的 compile() 方法。

const patternCache = {};

const cacheLimit = 10000;

let cacheCount = 0;

const compileGenerator = pattern => {

// 使用待匹配url作為cache key

 const cacheKey = pattern;

 const cache = patternCache[cacheKey] || (patternCache[cacheKey] = {});

 if (cache[pattern]) return cache[pattern];

 // 核心邏輯

 const compiledGenerator = pathToRegexp.compile(pattern);

 if (cacheCount < cacheLimit) {

   cache[pattern] = compiledGenerator;

   cacheCount++;

 }

 return compiledGenerator;

};

/**

* Public API for generating a URL pathname from a pattern and parameters.

*/

const generatePath = (pattern = "/", params = {}) => {

 if (pattern === "/") {

   return pattern;

 }

 const generator = compileGenerator(pattern);

 return generator(params, { pretty: true });

};

8 <Prompt />

用來做路由攔截的組件,唯一的作用就是在路由發生改變的時候攔截它並彈窗提醒。

8.1 props

屬性 when 默認為 true,如為 false 則不攔截任何路由變化。

屬性 message 為攔截時的提示信息,內容格式與 history.block() 方法參數格式相同,可為 string 或是 (location, action): (string | boolean) => {}。

8.2 源碼分析

非常簡單,一看就明白了。

// 重設message如果之前設置過就先清除

enable(message) {

   if (this.unblock) this.unblock();

   this.unblock = this.context.router.history.block(message);

 }

 disable() {

   if (this.unblock) {

     this.unblock();

     this.unblock = null;

   }

 }

 componentWillMount() {

   if (this.props.when) this.enable(this.props.message);

 }

 componentWillReceiveProps(nextProps) {

   if (nextProps.when) { // 當when發生變化變為true或是messge改變時觸發

     if (!this.props.when || this.props.message !== nextProps.message)

       this.enable(nextProps.message);

   } else {

     this.disable();

   }

 }

 componentWillUnmount() {

   this.disable();

 }

9 <Link />

對 a 標籤的封裝,實現 history 控制的路由跳轉。

9.1 props

繼承了 a 標籤的所有參數,使用屬性 to 來指明跳轉位置,replace 指明是否為重定向。

9.2 源碼分析

因為 onClick 事件優先級比 href 跳轉的高,所以優先處理。

 handleClick = event => {

   if (this.props.onClick) this.props.onClick(event);

   if (

     !event.defaultPrevented && // 如已取消默認操作則跳過

     event.button === 0 && // 忽略非左鍵單擊事件

     !this.props.target && // 如果設置了target參數則跳過

     !isModifiedEvent(event) // 忽略組合鍵

   ) {

     event.preventDefault();

     const { history } = this.context.router;

     const { replaceto } = this.props;

     if (replace) {

       history.replace(to);

     } else {

       history.push(to);

     }

   }

 };

如果 onClick 事件沒有執行,或是被 prevent 了,則採用 href 內的值來跳轉。

調用 history 的 createLocation() 生成跳轉後的 location,這個方法接收 4 個參數,(path,state,key,currentLocation)。因為走瀏覽器跳轉已經脫離了 history 的控制範圍,用於表示路由附加參數和當前路由表示的 state 與 key 參數無效,用 null 來佔位。

render() {

   const { replace, to, innerRef, ...props } = this.props;

   const { history } = this.context.router;

   //

   const location =

     typeof to === "string"

       ? createLocation(to, null, null, history.location)

       : to;

   // 根據變化後的location生成url

   const href = history.createHref(location);

   return (

     <a {...props} onClick={this.handleClick} href={href} ref={innerRef} />

   );

 }

10 withRouter()

高階函數,包裹組件,用來為組件添加當前路由狀態。

10.1 源碼分析

非常好理解,實際上就是使用 Route 組件對目標組件做了包裹處理。

為了避免組件被代理的時候出現靜態屬性丟失的情況(https://github.com/ReactTraining/react-router/pull/4838),使用了一個叫做 hoist-non-react-statics (字面直譯: 提升非react靜態屬性) 的庫(https://github.com/mridgway/hoist-non-react-statics)。

const withRouter = Component => {

 const C = props => {

   const { wrappedComponentRef, ...remainingProps } = props;

   return (

     <Route

       children={routeComponentProps => (

         <Component

           {...remainingProps}

           {...routeComponentProps}

           ref={wrappedComponentRef} // 提供被代理組件的ref

         />

       )}

     />

   );

 };

 C.displayName = `withRouter(${Component.displayName || Component.name})`;

 C.WrappedComponent = Component;

 C.propTypes = {

   wrappedComponentRef: PropTypes.func

 };

// 將傳入組件的靜態屬性提升裡C來

 return hoistStatics(C, Component);

};

參考

https://reacttraining.com/react-router/core/api

https://github.com/ReactTraining/react-router

相關焦點

  • React SSR 同構入門與原理
    import { BrowserRouter } from"react-router-dom";import Routes from"..import { StaticRouter } from"react-router-dom";import Routes from"..
  • Vue-Router路由模式
    --<router-link>默認會被渲染成一個`<a>`標籤--> <router-link to="/foo"> 睡覺 Foo</router-link> <router-link to="/bar"> 敲代碼 bar</router-link> </p&
  • vue-router 使用方法
    -- <router-link> 默認會被渲染成一個 `<a>` 標籤 --> <router-link to="/foo/panhe">Go to Foo</router-link> <router-link to="/bar">Go to Bar</router-link>
  • 如何在React應用中實現「使用GitHub登錄」
    編輯index.js,確保它看起來像這樣:import React from 'react';import ReactDOM from 'react-dom';import App from '.';import { BrowserRouter as Router, Route, Switch } from "react-router-dom";import Home from ".
  • Vue轉React不完全指北
    實現雙向綁定,可以不關心受控組件,v-model 相當於 onChange 的語法糖<input v-model="value" />複製代碼React單向數據流:萬物皆 Props,主要通過 onChange/setState()的形式該更新數據
  • 使用 React 實現頁面過渡動畫僅需四個步驟【譯】
    ReactTransitionGroup and Animated英文地址:https://hackernoon.com/animated-page-transitions-with-react-router-4-reacttransitiongroup-and-animated-1ca17bd97a1a
  • 適合Vue用戶的React教程,你值得擁有
    在Vue中我們可以使用provide/inject來實現跨多級組件進行傳值,就以上面所說場景為例,我們使用provide/inject來實現以下首先,修改App.vue內容為以下內容<template> <div id="app"> <router-view
  • 從Context源碼實現談React性能優化
    學完這篇文章,你會收穫: 了解Context的實現原理 源碼層面掌握React組件的render時機,從而寫出高性能的React組件 源碼層面了解那麼SCU是作用於這4個條件的哪個呢?
  • React — 端的編程範式
    我的確很需要這套腳手架,對於新手來說,整合 react / redux / react-redux / react-router / react-router-redux 的確還是蠻費勁的 —— 如果像我這麼偷懶,可能都沒辦法了解它們是什麼。
  • Java HashSet源碼分析
    根據 hashmap 的 put()方法源碼可知,實際上是覆蓋操作,雖然覆蓋對象的 key 和 value 都完全一致。源碼分析首先查看下源碼結構,發現該類源碼相對比較簡單3.1 構造方法3.2 添加元素 add()可以看到添加的對象直接作為 Hashmap 的 key, 而 value 是 final 修飾的空對象
  • vue-router│基本入門
    https://unpkg.com/vue-router/dist/vue-router.js<script src="/xxpath/vue-router.js"></script>npm install vue-router安裝之後,通過Vue.use()將路由作為中間件添加到vue項目中import
  • 82個Unity源碼大放送
    /s/1dDKY3Fr 密碼:c03q2-2 新仙劍奇俠傳連結:http://pan.baidu.com/s/1jH0fIuU 密碼:k5xp2-3 Unity3D 戰鬥卡牌《變身吧主公》客戶端+伺服器源碼連結:http://pan.baidu.com/s/1kUpot51 密碼:i02u2-4 降臨OL-U3D全套源碼
  • 「首席架構師推薦」關於React生態系統的一系列精選資源(2)
    React路由react-router - React的聲明性路由navi - React的聲明性異步路由- 用於構建Microsoft Web體驗的React組件react-bootstrap - 使用React構建的Bootstrap組件reactstrap - 簡單的React Bootstrap 4組件semantic-ui-react -
  • vue指南: Router路由基本使用
    /App'import router from './router' new Vue({ el:'#app', components:{ App }, template:'<App/>', router})App.vue裡:<template> <div> <div class="
  • 2021年,vue3.0 面試題分析(乾貨滿滿,內容詳盡)
    那麼接下來分析一波(本文講的非常詳細,爭取大家都能看懂,對大家有所幫助):1. Vue3.0裡有哪些是值得我們重點關注的點?2. Vue3.0中,哪些是面試官喜歡問的高頻率問題?(底層,源碼)a. 生成 Block treeVue.js 2.x 的數據更新並觸發重新渲染的粒度是組件級的,單個組件內部 需要遍歷該組件的整個 vnode 樹。
  • React Native實戰:配置和起步
    Node.js 需要 4.0 及其以上版本。安裝好之後,npm 也有了。nvm 是 Node.js 的版本管理器,可以輕鬆安裝各個版本的 Node.js 版本。安裝 nvm 可以通過 Homebrew 安裝:brew install nvm或者按照 這裡的方法安裝。
  • React系列十四 - React過渡動畫
    當然,我們可以通過原生的CSS來實現這些過渡動畫,但是React社區為我們提供了react-transition-group用來完成過渡動畫。一. react-transition-group介紹 React曾為開發者提供過動畫插件 react-addons-css-transition-group,後由社區維護,形成了現在的 react-transition-group。
  • Vue全家桶之vue-router路由的嵌套和命名路由
    上篇我們已經說過了vue-router的概念和基本使用步驟:首先引入vue-router相關的庫文件 ,然後添加路由連結 (router-link標籤的to屬性),其次添加路由填充位(router-view) ,定義路由組件 ,配置路由規則並創建路由實例,最後把路由掛載到 Vue 根實例中
  • 徹底搞懂 Node.js 中的 Require 機制(源碼分析到手寫實踐)
    require 加載普通文件模塊require 加載 C++ 擴展文件模塊require 加載原理(源碼分析與手寫)require 源碼解析圖畫了一個源碼導圖,可以直接跟著導圖學一遍配上文章講解,效果更佳。(導圖太大了上傳不清晰,需要找我要吧。)