当前位置: 首页 > wzjs >正文

扶贫网站建设的意义集团公司手机站网站

扶贫网站建设的意义,集团公司手机站网站,无忧seo博客,湖北网站设计制作公司有哪些2022 北京冬奥会开幕式 此前一直在疑惑,明明 pushState()、replaceState() 不触发 popstate 事件,可为什么 React Router 还能挂载对应路由的组件呢? 翻了一下 history.js 源码,终于知道原因了。 源码 假设项目路由设计如下&#…
2022 北京冬奥会开幕式

此前一直在疑惑,明明 pushState()replaceState() 不触发 popstate 事件,可为什么 React Router 还能挂载对应路由的组件呢?

翻了一下 history.js 源码,终于知道原因了。

源码

假设项目路由设计如下:

import { render } from 'react-dom'
import { BrowserRouter, Routes, Route } from 'react-router-dom'
import { Mine, About } from './routes'
import App from './App'const rootElement = document.getElementById('root')render(<BrowserRouter><Routes><Route path="/" exact element={<App />} /><Route path="/mine" element={<Mine />} /><Route path="/about" element={<About />} /></Routes></BrowserRouter>,rootElement
)

然后我们看下 <BrowserRouter /> 的源码(react-router-dom/modules/BrowserRouter.js),以下省略了一部分无关代码:

import React from 'react'
import { Router } from 'react-router'
import { createBrowserHistory as createHistory } from 'history'/*** The public API for a <Router> that uses HTML5 history.*/
class BrowserRouter extends React.Component {// 构建 history 对象history = createHistory(this.props)render() {// 将 history 对象等传入 <Router /> 组件return <Router history={this.history} children={this.props.children} />}
}// ...export default BrowserRouter

接着我们继续看下 <Router /> 组件的源码(react-router/modules/Router.js),如下:

import React from 'react'
import HistoryContext from './HistoryContext.js'
import RouterContext from './RouterContext.js'/*** The public API for putting history on context.*/
class Router extends React.Component {static computeRootMatch(pathname) {return { path: '/', url: '/', params: {}, isExact: pathname === '/' }}constructor(props) {super(props)this.state = {location: props.history.location}// 关键点:// 当触发 popstate 事件、// 或主动调用 props.history.push()、props.history.replace() 方法时,// 都会执行 history 对象的 listen 方法,使得执行 setState 强制更新当前组件this.unlisten = props.history.listen(location => {this.setState({ location })})}componentWillUnmount() {// 组件卸载时,解除监听if (this.unlisten) this.unlisten()}render() {return (// 由于 React Context 的特性,所有消费 RouterContext.Provider 的 Custom 组件// 在其 value 值发生变化时,都会重新渲染。// 当前 <Router /> 组件并没有做任何限制重新渲染的处理,// 因此每次 setState 都会引起 RouterContext.Provider 的 value 值发生变化。<RouterContext.Providervalue={{history: this.props.history,location: this.state.location,match: Router.computeRootMatch(this.state.location.pathname),staticContext: this.props.staticContext}}><HistoryContext.Provider children={this.props.children || null} value={this.props.history} /></RouterContext.Provider>)}
}export default Router

原因剖析

往下之前,如果对 History API 或者 URL Fragment 不了解的,可以看下这篇文章:History 对象及事件监听详解。

react-router-dom 引用了 history.js 库 ,它主要提供了三种方法:createBrowserHistorycreateHashHistorycreateMemoryHistory

它们用于构建对应模式的 history 对象(请注意,它有别于 window.history 对象),该对象的属性和方法可在 Devtools 中清晰地看到(如下图),也可查阅文档。这个太简单了,你们都懂,不说了。

本文讨论的是 History 模式,因而对应 createBrowserHistory 方法。

在构建项目路由时,选择 <BrowserRouter /> 组件,它内部是将通过 createBrowserHistory() 方法构造的 history 对象传递给 <Router /> 组件。

我们知道,在 React 应用中切换路由,它会加载对应的组件。我们知道 createBrowserHistory() 利用了 HTML5 History API 特性,但是主动调用 window.history.pushState()window.history.replaceState() 方法都不会触发 popstate 事件,因此,如果仅通过监听 popstate 事件是不能完全实现路由切换的。

那么 React Router 是如何解决问题的呢?

在前面的源码部分,其实已经添加了一些注解,<Router /> 组件它内部依赖于 Context 的 Provider/Comsumer 模式。因此,它只要做到 URL 发生变化时更新 Context.Providervalue 值即可,至于后续如何加载组件就交给 React 了(当然里面还包括 React Router 的路由匹配,但非本文讨论内容,不展开讲述)。

一般情况下,<BrowserRouter /> 都会作为整个项目的根路由,它包裹了一层 <Router /> 组件,<Router /> 组件在实例化时,设置了一个监听函数:

// props.history 就是通过 createBrowserHistory(props) 生成的对象
this.unlisten = props.history.listen(location => {// 回调函数的作用是,通过 setState 触发 Router 组件更新,// 使得 Provider 的 value 值发生变化,以带动 Consumer 的更新。this.setState({ location })
})
// this.unlisten 是一个函数,执行它内部会移除 popstate 事件监听器

Q:history.js 是如何做到每当 URL 发生变化,会触发这个回调函数的?

在 React 中是通过调用组件的 props.history.push()props.history.replace() 方法实现路由切换的。

我们来看一下 history.js 的源码(history/esm/history.js):

里面省略了一部分代码,然后分析顺序已经按顺序标注出来。

function createTransitionManager() {// ...function confirmTransitionTo(location, action, getUserConfirmation, callback) {var result = typeof prompt === 'function' ? prompt(location, action) : promptif (typeof result === 'string') {if (typeof getUserConfirmation === 'function') {getUserConfirmation(result, callback)} else {process.env.NODE_ENV !== 'production' ? warning(false, 'A history needs a getUserConfirmation function in order to use a prompt message') : void 0callback(true)}} else {// Return false from a transition hook to cancel the transition.callback(result !== false)}}var listeners = []function appendListener(fn) {var isActive = truefunction listener() {if (isActive) fn.apply(void 0, arguments)}// 添加监听器listeners.push(listener)return function () {isActive = false// 过滤重复的监听器listeners = listeners.filter(function (item) {return item !== listener})}}// 6️⃣ 执行 listeners 中所有的 listener 监听器,// 最后触发 <Router /> 中的回调函数 this.unlisten = props.history.listen(location => { this.setState({ location }) }) 逻辑function notifyListeners() {// 将类数组 arguments 转换为数组形式for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {args[_key] = arguments[_key]}listeners.forEach(function (listener) {// 回调函数将获得 location、action 两个参数return listener.apply(void 0, args)})}return {setPrompt: setPrompt,confirmTransitionTo: confirmTransitionTo,appendListener: appendListener,notifyListeners: notifyListeners}
}/*** Creates a history object that uses the HTML5 history API including* pushState, replaceState, and the popstate event.*/
function createBrowserHistory(props) {// ...// 创建 location 对象function getDOMLocation(historyState) {var _ref = historyState || {},key = _ref.key,state = _ref.statevar _window$location = window.location,pathname = _window$location.pathname,search = _window$location.search,hash = _window$location.hashvar path = pathname + search + hashif (basename) path = stripBasename(path, basename)return createLocation(path, state, key)}// ...// 创建 transitionManager 对象var transitionManager = createTransitionManager()// 5️⃣ 主要更新 history 对象,并调用 notifyListeners 方法function setState(nextState) {_extends(history, nextState)history.length = globalHistory.length// 执行 transitionManager 中的所有 listenerstransitionManager.notifyListeners(history.location, history.action)}// 3️⃣ popstate 事件监听器的处理函数function handlePopState(event) {// getDOMLocation 方法用于生成 location 对象,location: { hash, pathname, search, state }// handlePop 方法,主要是用于触发 setState 方法handlePop(getDOMLocation(event.state))}// 4️⃣ 用于调用 setState 方法function handlePop(location) {var action = 'POP'transitionManager.confirmTransitionTo(location, action, getUserConfirmation, function (ok) {if (ok) {setState({action: action,location: location})}})}// 7️⃣// 这里的 push 和 replace 方法,是利用了 window.history.pushState() 和 window.history.replaceState()// 他们不会触发 popstate 事件,因此无法执行 handlePopState 方法,因此我们需要主动执行 setState() 方法,进而// 执行 notifyListeners() 以使得 <Router /> 组件的回调被执行,使得组件进行更新。function push(path, state) {// ...var action = 'PUSH'var location = createLocation(path, state, createKey(), history.location)// 将会执行 confirmTransitionTo 的 callback 函数transitionManager.confirmTransitionTo(location, action, getUserConfirmation, function (ok) {if (!ok) returnvar href = createHref(location)var key = location.key,state = location.stateif (canUseHistory) {globalHistory.pushState({key: key,state: state},null,href)if (forceRefresh) {window.location.href = href} else {var prevIndex = allKeys.indexOf(history.location.key)var nextKeys = allKeys.slice(0, prevIndex + 1)nextKeys.push(location.key)allKeys = nextKeys// 调用 setState() 方法,然后里面会执行 notifyListeners 方法,并触发 listeners 的所有监听器setState({action: action,location: location})}} else {window.location.href = href}})}function replace(path, state) {// 与 push 方法同理,省略...}// 8️⃣// 这里的 go()、goBack()、goForward() 全是利用了 History API 的能力,// 他们都会触发 popstate 事件,因此都会执行 handlePopState 方法。function go(n) {globalHistory.go(n)}function goBack() {go(-1)}function goForward() {go(1)}var listenerCount = 0// 2️⃣ 注册/移除 popstate 事件监听器function checkDOMListeners(delta) {listenerCount += deltaif (listenerCount === 1 && delta === 1) {// 添加 popstate 事件监听器,执行 handlePopState 时将会触发 setStatewindow.addEventListener(PopStateEvent, handlePopState)if (needsHashChangeListener) window.addEventListener(HashChangeEvent, handleHashChange)} else if (listenerCount === 0) {// 移除事件监听器window.removeEventListener(PopStateEvent, handlePopState)if (needsHashChangeListener) window.removeEventListener(HashChangeEvent, handleHashChange)}}// 1️⃣ 设置监听器,以触发 <Router /> 组件中的回调函数function listen(listener) {// 往 transitionManager 中的 listeners 数组添加新的监听器 listener,// 其中 transitionManager 对象有这些方法:{ setPrompt, confirmTransitionTo, appendListener, notifyListeners }var unlisten = transitionManager.appendListener(listener)// 负责添加、移除 popstate 事件监听器checkDOMListeners(1)// 执行回调函数移除 listener 监听器return function () {checkDOMListeners(-1)unlisten()}}var history = {length: globalHistory.length,action: 'POP',location: initialLocation,createHref: createHref,push: push,replace: replace,go: go,goBack: goBack,goForward: goForward,block: block,listen: listen}return history
}

以下是 history.js 中创建的 historylocation 对象的一些属性和方法:

先回到 <Router /> 组件中的 history.listen(fn),它主要做几件事:

  • fn 保存在负责存储监听器的 listeners 数组中,未来它将会被 notifyListeners() 方法调用。
  • 注册 popstate 事件监听器,触发之后,会执行 notifyListeners() 方法
  • 在 React 组件中调用 props.history.push() 等方法,也将会触发 notifyListeners() 方法。
  • 执行 notifyListeners() 方法,会执行 listeners 中所有的 listener,因此 fn 将会被触发。
  • 执行 fn() 触发 Component 中的 setState() 方法更新 <Router /> 组件,即 Router.Providervalue 发生改变,那么 Router.Consumer 就会跟着更新

所以,React Router 是利用了 Context 的 Provider/Custom 特性,解决了 pushState/replaceState 不触发 popstate 事件时实现了路由切换的问题。

The end.



喜欢的朋友记得点赞、收藏、关注哦!!!


文章转载自:

http://PU9jxszX.pskjm.cn
http://fjWiF493.pskjm.cn
http://7tNgYQOf.pskjm.cn
http://kQBpiNdV.pskjm.cn
http://aRCVkLQO.pskjm.cn
http://bXmr3ewu.pskjm.cn
http://wqBWmtZZ.pskjm.cn
http://1S08Ar65.pskjm.cn
http://qyHth2Lr.pskjm.cn
http://6Za1GLiE.pskjm.cn
http://r5BCcMP8.pskjm.cn
http://YZvkhMSe.pskjm.cn
http://xoYQC1Ot.pskjm.cn
http://Va5P6zef.pskjm.cn
http://FvAWadYh.pskjm.cn
http://9EFk6xT7.pskjm.cn
http://ef1Zvqe1.pskjm.cn
http://MTZJrWSS.pskjm.cn
http://gDk6c5sS.pskjm.cn
http://0UTuYVoI.pskjm.cn
http://4rXVxGzR.pskjm.cn
http://dbpgis7K.pskjm.cn
http://NYnBBN7t.pskjm.cn
http://B4PCRelM.pskjm.cn
http://T7QaHgBQ.pskjm.cn
http://IP1EtvUs.pskjm.cn
http://tj7z43Vf.pskjm.cn
http://ztMIFO1i.pskjm.cn
http://kdw8siSP.pskjm.cn
http://wN8gaNce.pskjm.cn
http://www.dtcms.com/wzjs/738992.html

相关文章:

  • 苏州教育平台网站建设wordpress 导航函数
  • 电商网站设计规范西安做网站公司有哪些
  • 专业做互联网招聘的网站有哪些内容wordpress美容养生
  • 网站要素WordPress 云锁
  • 展示系统 网站模板免费下载电脑做系统网站
  • 海宁市网站建设为什么要建立网站
  • wordpress网站从零营销型网站的三大特点
  • 网站开发图片编辑技术支持 东莞网站建设 轴承
  • 西安网站建设开发查派贵阳利于优化的网站
  • 高端网站建设的小知识wordpress打不开自定义
  • wordpress子分类模板站长工具seo综合查询引流
  • 谷歌网站推广策略方案深圳做网站的公司
  • 西安网站建设推荐求几个好看的关键词
  • 提供网站制作公司地址下载百度2023最新版安装
  • 广东外贸网站推广公司汕头企业网页设计
  • 淄博圻谷网站建设制作网站定位与建设
  • 陕西交通建设集团网站体检wordpress小工具 样式
  • 上海网站制作福州wordpress删除管理站点链接
  • 快速建站网站啦手机兼职软件推荐app
  • 常德网站建设的策划方案网站建设 英文版
  • 专业一元夺宝网站建设如何分析网站开发语言
  • 中国网站设计模板网站开发详细流程
  • 四大门户网站创始人做风投要关注哪些网站
  • 西平企业网站建设网站图片验证码出不来
  • 如何建立网站视频开发一亩地多少钱
  • 深圳罗湖网站开发房产获客软件
  • 信息门户网站建设合同网站seo优化检测
  • 网页设计模板网站免费下载电商网站建设小兔仙
  • 网站建设费用账务处理网站备案ftp密码
  • 郑州外贸网站建设公司排名django个人博客网站开发部署源码