React Router源码解析

2023-11-15

虽然React Router已经到了V6版本了,但在我们项目中,目前主要用的还是React Router的V5版本,所以此处我们从V5版本着手,去了解前端路由的实现原理。

目标:希望收获

  • 前端路由的基本原理
  • React Router 的实现原理
  • React Router 的启发和借鉴

前置知识点

原生JS如何实现前端路由

前端路由,我们无非要实现两个功能:在不刷新页面的前提下,监听和匹配路由的变化,并根据路由匹配渲染页面内容

前端路由一般提供两种匹配模式, hash 模式和 history 模式,二者的主要差别在于对 URL 监听部分的不同,hash 模式监听 URL 的 hash 部分,也就是 # 号后面部分的变化

  • Hash 实现

    浏览器提供了onHashChange事件帮助我们直接监听 hash 的变化,并根据匹配到的 hash 的不同来渲染不同的内容

hash实现前端路由示例

<div class="box">
    <a href="#/route1">route1</a>
    <a href="#/route2">route2</a>
    <a href="#/route3">route3</a>
    <a href="#/route4">route4</a> </div>
<div id="root"></div>
 
const routes = [
      {
        path: '/route1',
        template: '<div>route1</div>',
      },
      {
        path: '/route2',
        template: '<div>route2</div>',
      },
      {
        path: '/route3',
        template: '<div>route3</div>',
      },
      {
        path: '/route4',
        template: '<div>route4</div>',
      },
];
const mount = document.getElementById('root');
window.addEventListener('hashchange', function (e) {
    const path = e.newURL.split('#')[1];
    const item = routes.find(function (item) {
        return item.path == path;
    });
    mount.innerHTML = item.template;
}); 
  • History 实现

    history 模式相较于 hash 最直接的区别就是跳转的路由不带 # 号

    H5 新引入的pushState()replaceState()popstate事件(只会在浏览器的某些行为下触发,如点击回退、前进按钮),能够让我们在不刷新页面的前提下,修改 URL,并监听到 URL 的变化,为 history 路由的实现提供了基础能力

history实现前端路由示例

<div class="box">
    <a href="/route1">route1</a>
    <a href="/route2">route2</a>
    <a href="/route3">route3</a>
    <a href="/route4">route4</a>
</div>
<div id="root"></div>
 
const routes = [
      {
        path: '/route1',
        template: '<div>route1</div>',
      },
      {
        path: '/route2',
        template: '<div>route2</div>',
      },
      {
        path: '/route3',
        template: '<div>route3</div>',
      },
      {
        path: '/route4',
        template: '<div>route4</div>',
      },
    ];
 
// 重写所有 a 标签事件
const elements = document.querySelectorAll('a[href]');
elements.forEach((el) =>
    el.addEventListener('click', (e) => {
        e.preventDefault(); // 阻止默认点击事件
        const test = el.getAttribute('href');
        history.pushState(null, null, el.getAttribute('href'));
        // 修改当前url(前两个参数分别是 state 和 title,这里暂时不需要用到
        onPopState();
        // 由于pushState不会触发onpopstate事件, 所以我们需要手动触发事件
    })
);
 
// onpopstate事件回调, 匹配路由的改变并渲染对应内容, 和 hash 模式基本相同
function onPopState() {
    const mount = document.getElementById('root');
 
    const item = routes.find(function (item) {
        return item.path == location.pathname;
    });
    mount.innerHTML = item.template;
}
 
// 绑定onpopstate事件, 当页面路由发生更改时(如前进后退),将触发popstate事件
window.addEventListener('popstate', onPopState); 

此处简单实现了history路由功能,但是这里有一个问题就是刷新后因为地址栏url原因会报错,找不到这个页面,这是由于刷新的时候是重载,重新向网站目录查找文件,而我们当前目录并没有这个文件资源所以导致报错。需要后台拦截

原因分析
如果直接访问了首页就是访问 index.html ,此时在服务器是有匹配的,不会有404。前端跳转路由变化,是前端的history api来控制的,和服务器没有关系。如果直接访问了https://xxx.com/具体的页面路径,此时服务器是没有匹配的资源的,就会报 404,所以需要配置服务,当 404的时候,配置回退路由,指向 index.html,让路由在前端控制。
对于nginx可以这么配置:

location / {
    try_files $uri $uri/ /index.html;<br/>
} 

React.createContext

使用Context来实现跨层级的组件数据传递

Context 提供了一个无需为每层组件手动添加props,就能在组件树间进行数据传递的方法,该组件导出两个对象Provider提供数据,Consumer消费数据

React.createContext的简单使用

// 创建上下文
let {Provider, Consumer} = React.createContext()
 
// 假设我们有很多个组件,我们只需要在父组件使用Provider提供数据,然后我们就可以在子组件任何位置使用Consumer拿到数据,不存在跨组件的问题
// 提供数据
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
let {Provider, Consumer} = React.createContext()
// 父组件
function Parent (props) {
    return (
        <div>
            <div>Parent: </div>
           <Son></Son>
        </div>
    )
}
// 子组件
function Son (props) {
    return (
        <div>
            <div>Son: </div>
            <Child></Child>
        </div>
 
    )
}
// 孙子组件
function Child (props) {
    return (
        <Consumer>
            // 子组件必须渲染一个函数,函数的参数就是Context得值
            {value => <div>
                value: {value}
            </div>}
        </Consumer>
    )
}
ReactDOM.render(<Provider value="1">
    <Parent />
</Provider>, document.getElementById('root')); 

原理解析:创建两个组件ProviderConsumerProvider有一个参数value,在Provider组件内遍历子组件,如果组件是Consumer,就返回一个组件,并将value作为值传递给新创建的子组件Consumer

组件国际化使用Context

// 组件库使用了 contextType 可以简化 context 的使用,不使用 consumer 也可以共享变量
 
// LocaleContext组件
export const LocaleContext = React.createContext({ language: 'zh-cn' });
 
// Upload组件
export class Upload extends React.Component<UploadProps, UploadState> {
  ...
  // 申明静态变量、contextType 将 context 直接赋值于 contextType
  static contextType = LocaleContext;
   
  ...
 
  renderFileList() {
    const { fileList } = this.props;
    const data: FileListItem[] = fileList || this.state.fileList;
    // 可以直接 访问 this.context 获取共享变量、这样就可以不使用 consumer
    const localeContent = locales[this.context.language];
 
    return (
      <ul className={`${this.styleName}-fileList`}>
        {data.map(item => (
          <li key={item.uid}>
            <span>{item.name}</span>
            <div>
              <span className={`${this.styleName}-deleteFile`} onClick={this.handleDelete(item)}>
                {localeContent['component.upload.delete']}
              </span>
            </div>
          </li>
        ))}
      </ul>
    );
  }
 
  render() {
    ...
  }
}
 
// 使用
<LocaleContext.Provider value={{ language: language }}>        
    <Layout>
        <Upload></Upload>
    </Layout>
</LocaleContext.Provider> 

History

history库是react-router依赖的核心库,它将应用的history做了统一的抽象,包含一系列统一的属性和方法,支持浏览器的BrowserHistory、HashHistory以及服务端的MemoryHistory。

history库对自己的描述是:通过history可以在任何运行 JavaScript的地方轻松管理会话历史记录。一个history对象可以抽象出各种环境中的差异,并提供一个最小的API,使我们可以管理历史记录堆栈,导航和在会话之间保持状态。

react-router源码

react-router仓库目录

├── packages
    ├── react-router    // 核心、公用代码
    ├── react-router-config   // 路由配置
    ├── react-router-dom   // 浏览器环境路由
    └── react-router-native   // React Native 路由 

react-router的仓库是以monorepo(单一代码库)的方式管理包的,在这个仓库里面同时包含有react-routerreact-router-domreact-router-nativereact-router-config这四个包,其中react-router是核心包, react-router-domreact-router-native都依赖于它

react-routerreact-router-dom 的区别:

  • react-router: 实现了路由的核心功能

  • react-router-dom: 基于react-router,加入了在浏览器运行环境下的一些功能

    例如:

    • Link组件,会渲染一个a标签,Link组件源码a标签行;
    • BrowserRouter和HashRouter组件,前者使用pushState和popState事件构建路由,后者使用window.location.hash和hashchange事件构建路由。
  • react-router-native: 基于react-router,类似react-router-dom,加入了react-native运行环境下的一些功能。

react-router-dom依赖react-router,所以我们使用npm安装依赖的时候,只需要安装相应环境下的库即可,不用再显式安装react-router

React Router 的源码实现

在前面我们用原生JS实现了一个基本的前端路由,现在介绍 React Router 的源码实现,通过比较二者的实现方式,分析 React Router 实现的动机和优点

我们项目里是怎么用到React-router的?

const routes = [
  { path: '/login', component: Login },
  { path: '/', component: Main }
];
const entry = () => <Switch>
    {routes.map((route: { path: string; component: any }) => (
        <Route key={route.path} path={route.path} component={route.component} />
    ))}
</Switch>
 
history = createHashHistory();
 
app.navigator = new Navigator(this.history);
 
ReactDOM.render(
    <Router history={history}>
        <Switch>
            <Route component={entry} />
        </Switch>
    </Router>,
    document.querySelector(selector)
); 

可以看到我们用到了Router组件,Switch组件和Route组件,其中SwitchRoute都被Router所包裹。React Router 的组件通常分为三种:

React Router组件.png

  • 路由器组件: 路由器组件的作为根容器组件, 路由组件必须被包裹在内才能够使用。
  • 路由匹配组件: 路由匹配组件通过匹配 path,渲染对应组件。
  • 导航组件: 导航组件起到类似 a 标签跳转页面的作用

我们可以整体看看react-router的整个流程是怎样实现的:

20210330211645334.png

所有的路由组件都必须被包裹在这两个组件中才能使用

源码

import React from "react";
import { Router } from "react-router";
import { createBrowserHistory as createHistory } from "history";
 
class BrowserRouter extends React.Component {
  history = createHistory(this.props);
 
  render() {
    return <Router history={this.history} children={this.props.children} />;
  }
}
 
export default BrowserRouter;
 
// createBrowserHistory解析
import { createBrowserHistory } from 'history';
// 创建history实例
const history = createBrowserHistory();
// 获取当前 location 对象,类似 window.location
const location = history.location;
// 设置监听事件回调,回调接收两个参数 location 和 action
const unlisten = history.listen((location, action) => {  
  console.log(location.pathname, location.state);
});
 
// 可以使用 push 、replace、go 等方法来修改会话历史
history.push('/home', { some: 'state' });                
// 如果要停止监听,调用listen()返回的函数.
unlisten(); 

源码

import React from "react";
import { Router } from "react-router";
import { createHashHistory as createHistory } from "history";
 
class HashRouter extends React.Component {
  history = createHistory(this.props);
 
  render() {
    return <Router history={this.history} children={this.props.children} />;
  }
}
 
export default HashRouter; 

不管是HashRouter,还是BrowserRouter,底层都是Router组件。 Router 其实是两个 context(HistoryContextRouterContext)组成的,由于React16和15的Context互不兼容, 所以React Router使用了一个第三方的 context 以同时兼容 React 16 和 15,这个 context 基于mini-create-react-context实现, 这个库也是React context的Polyfil, 所以可以直接认为二者用法相同,react router 团队已经在计划用React.createContextAPI 来创建它们。HistoryContext的名称是Router-HistoryRouterContext的名称是Router

Router 组件会调用 history 的 listen 方法进行 路由监听,将监听到的 location 的值放大 RouterContext 中。

源码

import React from "react";
import PropTypes from "prop-types";
import warning from "tiny-warning";
 
import HistoryContext from "./HistoryContext.js";
import RouterContext from "./RouterContext.js";
 
/**
 * The public API for putting history on context.
 */
class Router extends React.Component {
  // 生成根路径的 match 对象
  static computeRootMatch(pathname) {
    return { path: "/", url: "/", params: {}, isExact: pathname === "/" };
  }
 
  constructor(props) {
    super(props);
 
    // state 初始化,Router组件维护了一个内部状态location对象,初始值为外部传入history 
    this.state = {
      location: props.history.location
    };
 
    // This is a bit of a hack. We have to start listening for location
    // changes here in the constructor in case there are any <Redirect>s
    // on the initial render. If there are, they will replace/push when
    // they mount and since cDM fires in children before parents, we may
    // get a new location before the <Router> is mounted.
 
    // _isMounted 表示组件是否加载完成
    this._isMounted = false;
    // 组件未加载完毕,但是 location 发生的变化,暂存在 _pendingLocation 字段中
    this._pendingLocation = null;
 
    // 没有 staticContext 属性,表示是 HashRouter 或是 BrowserRouter
    if (!props.staticContext) {
 
      // 调用 history 的 listen 方法,用来更新当前Router内部状态中的location的
      this.unlisten = props.history.listen(location => {
        if (this._isMounted) {
          // 组件加载完毕,将变化的 location 方法 state 中
          this.setState({ location });
        } else {
          // 如果组件未挂载, 就先把 location 存起来, 等到 didmount 阶段再 setState 
          this._pendingLocation = location;
        }
      });
    }
  }
 
  componentDidMount() {
    this._isMounted = true;
 
    if (this._pendingLocation) {
      this.setState({ location: this._pendingLocation });
    }
  }
 
  // 卸载监听器
  componentWillUnmount() {
    if (this.unlisten) this.unlisten();
  }
 
  render() {
    return (
      <RouterContext.Provider
        value={{
          // 根据 HashRouter 还是 BrowserRouter,可判断 history 类型
          history: this.props.history,
          // 这个 location 就是监听 history 变化得到的 location
          location: this.state.location,
          // 是否为根路径
          match: Router.computeRootMatch(this.state.location.pathname),
          // 只有 StaticRouter(服务端渲染) 会传 staticContext
          // HashRouter 和 BrowserRouter 都是 null
          staticContext: this.props.staticContext
        }}
      >
        <HistoryContext.Provider
          // 透传子组件
          children={this.props.children || null}
          value={this.props.history}
        />
      </RouterContext.Provider>
    );
  }
}
 
export default Router; 

现在我们明白为什么路由组件要求被包裹在路由器容器组件内才能使用,因为路由信息都由外层的容器组件通过 context 的方式,传递给所有子孙组件,子孙组件在拿到当前路由信息后,才能匹配并渲染出对应内容。此外在路由发生改变的时候,容器组件会通过setState()的方式,触发子组件重新渲染。

这里我们完成了监听这一步,在 React Router 中,这一步由 history 库来完成,代码内调用了history.listen 就完成了对几种模式路由的监听。下面我们看看React Router里是如何进行匹配的。

的三种匹配方式

匹配模式:
// 精确匹配
// 严格匹配 匹配反斜杠,规定是否匹配末尾包含反斜杠的路径,如果strict为true,则如果path中不包含反斜杠结尾,则他也不能匹配包含反斜杠结尾的路径
// 大小写敏感
<Route path="/user" exact component={User} />
<Route path="/user" strict component={User} />
<Route path="/user" sensitive component={User} />
 
路径 path 写法:
// 字符串形式
// 命名参数
// 数组形式
<Route path="/user" component={User} />
<Route path="/user/:userId" component={User} />
<Route path={["/users", "/profile"]} component={User} />
 
渲染方式:
// 通过子组件渲染
// 通过 props.component 渲染
// 通过 props.render 渲染
<Route path='/home'><Home /></Route>
<Route path='/home' component={Home}></Route>
<Route path='/home' render={() => <p>home</p>}></Route> 

源码

import React from "react";
import { isValidElementType } from "react-is";
import PropTypes from "prop-types";
import invariant from "tiny-invariant";
import warning from "tiny-warning";
 
import RouterContext from "./RouterContext.js";
import matchPath from "./matchPath.js";
 
/**
 * The public API for matching a single path and rendering.
 */
class Route extends React.Component {
  render() {
    return (
      <RouterContext.Consumer>
        {context => {
          invariant(context, "You should not use <Route> outside a <Router>");
 
          // 可以看出,用户传的 location 覆盖掉了 context 中的 location
          const location = this.props.location || context.location;
 
          // 如果有 computedMatch 就用 computedMatch 作为结果
          // 如果没有,则判断是否有 path 传参
          // matchPath 是调用 path-to-regexp 判断是否匹配
          // path-to-regexp 需要三个参数
          // exact: 如果为 true,则只有在路径完全匹配 location.pathname 时才匹配
          // strict: 如果为 true 当真实的路径具有一个斜线将只匹配一个斜线location.pathname
          // sensitive: 如果路径区分大小写,则为 true ,则匹配
          const match = this.props.computedMatch
            ? this.props.computedMatch // <Switch>组件中已经计算了match
            : this.props.path
            ? matchPath(location.pathname, this.props)
            : context.match;
 
          // 把当前的 location 和 match 拼成新的 props,这个 props 会通过 Provider 继续向下传
          // props 就是更新后的 context
          // location 做了更新(有可能是用户传入的location)
          // match 做了更新
          const props = { ...context, location, match };
 
          // 三种渲染方式
          let { children, component, render } = this.props;
 
          // Preact uses an empty array as children by
          // default, so use null if that's the case.
          // children 默认是个空数组,如果是默认情况,置为 null
          if (Array.isArray(children) && children.length === 0) {
            children = null;
          }
 
          return (
            // RouterContext 中更新了 location, match
            <RouterContext.Provider value={props}>
              {props.match
              // 首先判断的是有无 children
                ? children
                  // 如果 children 是个函数,执行,否则直接返回 children
                  ? typeof children === "function"
                  : children(props)
                  : children
                  // 如果没有 children,判断有无 component
                : component
                  // 有 component,重新新建一个 component
                  ? React.createElement(component, props)
                  // 没有 component,判断有无 render
                  : render
                  // 有 render,执行 render 方法
                  ? render(props)
                  // 没有返回 null
                  : null
 
                // 这里是不 match 的情况,判断 children 是否函数
                : typeof children === "function"
                // 是的话执行
                ? children(props)
                : null}
            </RouterContext.Provider>
          );
        }}
      </RouterContext.Consumer>
    );
  }
}
 
export default Route; 

matchPath匹配路径

//const match = this.props.computedMatch
//  ? this.props.computedMatch // <Switch> already computed the match for us
//  : this.props.path
//  ? matchPath(location.pathname, this.props)  // 判断 path 是否和 location 中的路径项匹配
//  : context.match;
 
 
function matchPath(pathname, options = {}) {
  // 如果 options 传的是个 string,那默认这个 string 代表 path
  // 如果 options 传的是个 数组,那只要有一个匹配,就认为匹配
  if (typeof options === "string" || Array.isArray(options)) {
    options = { path: options };
  }
 
  // exact, strict, sensitive 默认 false
  const { path, exact = false, strict = false, sensitive = false } = options;
 
  // 转化成数组进行判断
  const paths = [].concat(path);
 
  return paths.reduce((matched, path) => {
    if (!path && path !== "") return null;
 
    // 只要有一个 match,直接返回,认为是 match
    if (matched) return matched;
 
    // regexp 是正则表达式
    // keys 是切割出来的得 key 的值
    const { regexp, keys } = compilePath(path, {
      end: exact,
      strict,
      sensitive
    });
 
    // exec() 该方法如果找到了匹配的文本的话,则会返回一个结果数组,否则的话,会返回一个null
    const match = regexp.exec(pathname);
 
    if (!match) return null;
 
    // url 表示匹配到的部分
    const [url, ...values] = match;
 
    // pathname === url 表示完全匹配
    const isExact = pathname === url;
 
    if (exact && !isExact) return null;
 
    return {
      path, // the path used to match
      url: path === "/" && url === "" ? "/" : url, // the matched portion of the URL
      isExact, // whether or not we matched exactly
      // 匹配到的参数
      params: keys.reduce((memo, key, index) => {
        memo[key.name] = values[index];
        return memo;
      }, {})
    };
  }, null);
}
const cache = {};
const cacheLimit = 10000;
let cacheCount = 0;
 
function compilePath(path, options) {
  // 一个全局缓存,确保计算出的结果能够得到复用
  const cacheKey = `${options.end}${options.strict}${options.sensitive}`;
  const pathCache = cache[cacheKey] || (cache[cacheKey] = {});
 
  if (pathCache[path]) return pathCache[path];
 
  const keys = [];
 
  // pathToRegexp 就是将路径字符串转为正则表达式
  // path 就是配置的路径
  // regexp 是一个正则表达式
  const regexp = pathToRegexp(path, keys, options);
  const result = { regexp, keys };
 
  // 最多缓存 10000 个
  if (cacheCount < cacheLimit) {
    pathCache[path] = result;
    cacheCount++;
  }
 
  return result;
} 

从源码我们可以看到:

  • Route的componentrenderchildren三个属性是互斥的
  • 优先级children>component>render
  • children在无论路由匹配与否,都会渲染

匹配流程:

20210331000539695.png

通过以上解析可以了解到

  • React-Router通过监听location变化触发刷新,实现路由更新
  • 利用React的Context机制,实现嵌套路由分析,和状态传递
  • Route组件中componentrenderchildren三个属性的渲染机制
  • 所有的机制都在render中,所以能够在渲染时进行动态路由

招贤纳士

青藤前端团队是一个年轻多元化的团队,坐落在有九省通衢之称的武汉。我们团队现在由 20+ 名前端小伙伴构成,平均年龄26岁,日常的核心业务是网络安全产品,此外还在基础架构、效率工程、可视化、体验创新等多个方面开展了许多技术探索与建设。在这里你有机会挑战类阿里云的管理后台、多产品矩阵的大型前端应用、安全场景下的可视化(网络、溯源、大屏)、基于Node.js的全栈开发等等。

如果你追求更好的用户体验,渴望在业务/技术上折腾出点的成果,欢迎来撩~ yan.zheng@qingteng.cn

附:V6对比V5的变化

  1. Switch 名称变为 Routes

  2. Route 不再支持子组件,改为使用 element 属性

  3. exact 属性不再需要

  4. Route 先后顺序不再重要,React Router 能够自动找出最优匹配路径

  5. 保留 Link, NavLink, 但是 NavLink 的 activeClassName 属性被移除。

  6. 移除 Redirect组件,改为使用 Navigate

  7. 嵌套路由改为相对匹配

    a. 嵌套路由 必须 放在 中,且使用相对路径,不再像 v5 那样必须提供完整路径

    b. 如果有 Link 组件,那么其 to 属性也使用相对路径

    c. 使用 Outlet 组件,此组件是一个占位符,告诉 React Router 嵌套的内容应该放到哪里用

  8. useNavigate 实现编程式导航,useHistory 被移除

参考

React Router v5 和 v6的比较
React-router源码—好家伙!
深入浅出解析React Router 源码

  1. 移除 Redirect组件,改为使用 Navigate

  2. 嵌套路由改为相对匹配

    a. 嵌套路由 必须 放在 中,且使用相对路径,不再像 v5 那样必须提供完整路径

    b. 如果有 Link 组件,那么其 to 属性也使用相对路径

    c. 使用 Outlet 组件,此组件是一个占位符,告诉 React Router 嵌套的内容应该放到哪里用

  3. useNavigate 实现编程式导航,useHistory 被移除

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

React Router源码解析 的相关文章

  • 了解设置 JQuery 变量

    了解设置 JQuery 变量 最近 我通过在 StackOverflow 上遇到的另一个问题寻找帮助 了解到如何设置 JQuery 变量 如下所示 您可以通过简单地调用变量来创建输入字段 并且锚变量似乎也定义了样式 var clicked
  • 从数据 URI 解码 QR 码

    我尝试从数据 uri 中解码二维码 var dataUri data image gif base64 R0lGODdh9gD2AIAAAAAAAP ywAAAAA9gD2AAAC decodeQrCode dataUri cb 我已经尝试
  • 为什么 JavaScript base-36 转换看起来不明确

    我目前正在编写一段使用 Base 36 编码的 JavaScript 我遇到了这个问题 parseInt welcomeback 36 toString 36 看来要回归了 welcomebacg 我在 Chrome 开发者控制台和 Nod
  • 如何重定向到 instagram://user?username={username}

    我的 html 页面上有这个链接 可以在特定用户上打开 Instagram 应用程序 a href Link to Instagram Profile a 我一直在寻找自动运行 url instagram user username USE
  • 我想检查 $('#td1').text() === "x" 是否?

    我想检查innerHtml是否有X或O 所以我不能再次添加任何其他东西 但它不起作用 添加检查代码后它就停止了 我在这里尝试做一个简单的XO游戏来更熟悉javascript和jquery 我也不确定是否可以用 jQuery 做到这一点
  • 使用 JavaScript 使链接保持活动状态并在单击时显示悬停效果

    I am struggling to make this work I d like to make it where if O F is clicked the hover state stays active if another li
  • 检查 JavaScript 字符串是否为 URL

    JavaScript 有没有办法检查字符串是否是 URL 正则表达式被排除在外 因为 URL 很可能是这样写的stackoverflow 也就是说它可能没有 com www or http 如果你想检查一个字符串是否是有效的 HTTP UR
  • 在 Wordpress 站点中进行 AJAX 调用时出现问题

    我在使用 Wordpress 站点功能的 AJAX 部分时遇到了一些问题 该功能接受在表单上输入的邮政编码 使用 PHP 函数来查找邮政编码是否引用特定位置并返回到该位置的永久链接 我的第一个问题是关于我构建的表单 现在我的表单操作是空白的
  • 如何将背景图像仅应用于一个反应页面而不是整个应用程序?

    注册页面示例 register background image linear gradient to right ff5722 0 ff9800 100 margin top 150px important div div div div
  • React - 无法读取未定义的属性[重复]

    这个问题在这里已经有答案了 通常 当我单击子组件中的菜单项时 它会调用 this handlesort 这是一个本地函数 处理排序从我的父组件中获取 onReorder 属性 onReorder 调用名为 reOrder 的本地函数 它设置
  • 通过 CDN 使用 Dojo 时如何加载自定义 AMD 模块?

    我正在使用 google 的 CDN 并尝试使用他们的加载程序加载我自己的 AMD 模块 我知道我做错了什么 但我被困住了 有任何想法吗
  • Firefox 书签探索未超过 Javascript 的第一级

    我已经编写了一些代码来探索我的 Firefox 书签 但我只获得了第一级书签 即我没有获得文件夹中的链接 e g 搜索引擎 雅虎网站 谷歌网站 在此示例中 我只能访问 Search engines 和 google com 不能访问 yah
  • 提交表单并重定向页面

    我在 SO 上看到了很多与此相关的其他问题 但没有一个对我有用 我正在尝试提交POST表单 然后将用户重定向到另一个页面 但我无法同时实现这两种情况 我可以获取重定向或帖子 但不能同时获取两者 这是我现在所拥有的
  • Grails 在 javascript 内的 GSP 站点中使用 grails var

    我有一个在 GSP 文件中的 javascript 代码中使用 grails 变量值的问题 例如 我有一个会话值session getAttribute selectedValue 我想在 javascript 代码部分使用这个值 我现在的
  • Angular 2+ 安全性;保护服务器上的延迟加载模块

    我有一个 Angular 2 应用程序 用户可以在其中输入个人数据 该数据在应用程序的另一部分进行分析 该部分仅适用于具有特定权限的人员 问题是我们不想让未经授权的人知道how我们正在分析这些数据 因此 如果他们能够在应用程序中查看模板 那
  • Javascript 数组到 VBScript

    我有一个使用 Javascript 构建的对象数组 我需要使用 VBScript 读取它 如下例所示 我找不到在 VbScript 代码中循环遍历数组的方法myArray object 这个例子是我的问题的简化 我无法更改页面的默认语言 这
  • Javascript 纪元时间(以天为单位)

    我需要以天为单位的纪元时间 迄今为止 我已经看到过有关如何翻译它的帖子 但几天后就没有了 我对纪元时间很不好 我怎么能得到这个 我需要以天为单位的纪元时间 我将解释为您想要自纪元以来的天数 纪元本身是第 0 天 或第 1 天的开始 无论您如
  • 为什么 jquery 没有检测到单选按钮未被选中的情况? [复制]

    这个问题在这里已经有答案了 可能的重复 JQuery radioButton change 在取消选择期间不会触发 https stackoverflow com questions 5176803 jquery radiobutton c
  • 将 MQTTNet 服务器与 MQTT.js 客户端结合使用

    我已经启动了一个 MQTT 服务器 就像this https github com chkr1011 MQTTnet tree master例子 该代码托管在 ASP Net Core 2 0 应用程序中 但我尝试过控制台应用程序 但没有成
  • 如何从图像输入中获取 xy 坐标?

    我有一个输入设置为图像类型

随机推荐

  • 修复ROS2使用zsh无法用tab补全 ros2 指令的相关问题

    安装完ROS2 后 改用zsh发现无法使用tab补全 ros2相关指令 现记录下修复办法 首先 安装python3 argcomplete sudo apt install python3 argcomplete 然后 在 zshrc里面添
  • STM32 CAN通信的学习笔记总结(从小白开始)

    知识来源于互联网 回馈于互联网 目录 1 总体概述 1 1 基本概念 1 2 通讯方式 1 3 为什么使用CAN 1 4 CAN的协议及组成 2 上帝视角看CAN的通讯过程 2 1 数据传输原理实现 2 2 通信的整个过程 2 2 1 空闲
  • Python读写excel文件

    1 使用pandas库读取Excel 最常用 pandas可以读取各种各样格式的数据文件 一般输出dataframe格式 如 txt csv excel json 剪切板 数据库 html hdf parquet pickled文件 sas
  • FSCapture注册码

    企业版序列号 name bluman serial 序列号 注册码 VPISCJULXUFGDDXYAUYF 转载于 https www cnblogs com wshsdlau p 4396184 html
  • HTML<DIV>常用标签

    目录 1 什么是DIV 1 1 div是什么意思 1 2 div标签怎么用 1 3 div布局优势 1 4 DIV作用是什么 1 5 有哪些DIV方式 1 5 1行内样式 1 5 2内嵌样式 1 5 3外部样式 1 6 样式使用规则 2 D
  • 使用队列实现stack

    两个队列实现一个stack q1只保持一个元素即可 多余的转换到q2当中 出队列元素 有两种情况 q1不为空 直接出队列 如果连续出队列 q1可能为空 需要q2的部分元素放到q1当中去 说白了就是元素捣鼓来捣鼓去的问题即可 class My
  • Linux-安装redis6.2.1及主备复制模式(replication)

    Linux 安装redis6 2 1 下载redis6 2 1资源 上传至安装目录 解压及编译 解压 修改名称 编译 修改配置文件 主节点 从节点 启动及测试 启动 主节点 从节点 测试 下载redis6 2 1资源 地址 https re
  • 华为数通方向HCIP-DataCom H12-821题库(单选题:101-120)

    第101题 可用于多种路由协议 由 if match 和 apply 子句组成的路由选择工具是 A route policy B IP Prefix C commnityfilter D as path filter 答案 A 解析 Rou
  • QT对话框去掉帮助和关闭按钮 拦截QT关闭窗口的CloseEvent

    建了一个对话框 我不想把边框去掉 只想去掉关闭按钮 setWindowFlags windowFlags Qt WindowCloseButtonHint Qt WindowContextHelpButtonHint 结果那个问号的按钮去掉
  • c++序列化以及反序列化实现

    1 什么是序列化和反序列化 当我们在写程序时 比如说我们自定义了一个实体类Person 然后在程序中创建一个该实体类对象 并给对象赋了一些值 但是我们想将这些数据发给我们的其他的程序员朋友 让他们也可以调用我们创建的这个实体类并使用我们的数
  • 数据库实时同步利器——CDC(变化数据捕获技术)

    在进行数据ETL过程中 我们经常需要通过周期性的定时调度将业务数据按照T 1的方式同步到数据仓库中 进行数据分析处理 最终通过BI报表展示给最终用户 但这种方式实时性较差 用户往往只能看到昨天的数据 会影响用户决策的及时性 而如果用户要近实
  • 更换持续集成工具,从 Travis 到 Github Actions

    我真傻 真的 单单受文档的推荐就选择了 Travis 作为部分项目的持续集成工具 没有料到它早已于 2020 年 12 月更换了免费政策 不再为开源项目提供免费的用于持续集成使用的 Credits 了 当赠送的 10000 个点数用完 就需
  • 【踩坑经历】Java Long 类型传给前端损失精度的问题

    最近在做一个 SpringBoot Vue 的项目 持久层框架用的是 MyBatis Plus 然后遇到了一个问题 一起来看下怎么回事 这个项目就是一个文章收藏器 可以收藏一些技术文章 然后可以选择星标 以便查找这篇文章 那么点击星标的按钮
  • 服务器的tomcat调优和jvm调化

    下面讲述的是tomcat的优化 及jvm的优化 Tomcat 的缺省配置是不能稳定长期运行的 也就是不适合生产环境 它会死机 让你不断重新启动 甚至在午夜时分唤醒你 对于操作系统优化来说 是尽可能的增大可使用的内存容量 提高CPU 的频率
  • 操作系统12----进程间通信IPC

    进程间通信IPC 1 进程通信 IPC Inter Process Communication 1 1直接通信 1 2间接通信 1 3阻塞通信 1 4非阻塞通信 2 信号 Signal 3 管道 pipe 4 消息队列 5 共享内存 1 进
  • 基于面板数据的熵值法介绍与实现

    熵值法是一种基于信息熵理论的客观赋值方法 即数据越离散 所含信息量越多 对综合评价影响越大 目录 一 基于面板数据熵值法介绍 二 R语言实现 参考文献 一 基于面板数据熵值法介绍 传统的熵值法有个弊端 只能针对于截面数据 即根据某一年 k
  • MySQL创建表时提示:1067 - Invalid default value for ‘sex‘

    问题 在创建表的时候如果有中文 则会提示 1067 Invalid default value for sex 比如 创建信息表 create table userInfo card id int primary key auto incr
  • unity 内嵌网页简单流程(3D WebView 3.14.1)

    我是用于 web 平台 特此记录 3D WebView 主要实现在unity 中制作网页浏览器 可使用平台 很强大 其他类似插件都有平台缺陷 Android iOS UWP Hololens Windows macOS WebGL 0 插件
  • 制造行业主数据同步集成

    主数据是描述企业核心业务实体的数据 是企业核心业务的主要构成 各个订单 合同以及业务的主体 在企业内部被重复 共享应用的数据 主数据跨越企业各个业务部门以及各类业务系统 是应用系统间数据交互的基础 近期一直北方某制造业进行主数据治理工作 谈
  • React Router源码解析

    虽然React Router已经到了V6版本了 但在我们项目中 目前主要用的还是React Router的V5版本 所以此处我们从V5版本着手 去了解前端路由的实现原理 目标 希望收获 前端路由的基本原理 React Router 的实现原