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源码解析 的相关文章

  • HTML/VBA Click 事件未触发

    这是我第一次在 StackOverflow 上发布问题 到目前为止 我已经能够通过 VBA 帮助论坛解决我的大部分问题 我的问题很简单 我有一个自动数据拉取 我需要在其中导出数据 我过去曾在这方面取得过成功 但这次略有不同 我尝试单击以生成
  • Node js 使用中间件重定向进行过多重定向

    在我的 Node js 应用程序 我使用的是express 4 x 中 我想检查用户是否已登录 如果用户未登录 我想重定向到我的登录页面 然后我在中间件中这样做 服务器 js app use function req res next if
  • 在网页上的文本框中键入内容时删除所有空格

    我如何在用户打字时即时删除输入到文本框中的空格 function var txt myTextbox var func function txt val txt val replace s g txt keyup func blur fun
  • jQuery 选择 # id 以单词为前缀,计数器为后缀

    有没有办法用 jQuery 选择所有带有前缀 my 和后缀 0 9 的 id 像这样的 my 1 4 还是可以用循环来实现 div div div div div div div div div div 第一个想法 似乎效果很好 div i
  • 如何按照编写的顺序迭代 javascript 对象属性

    我发现了代码中的一个错误 我希望通过最少的重构工作来解决该错误 此错误发生在 Chrome 和 Opera 浏览器中 问题 var obj 23 AA 12 BB iterating through obj s properties for
  • 使用 CryptoJS 更改密钥 [重复]

    这个问题在这里已经有答案了 我正在使用 CryptoJS 来加密和解密文本 在这里 我只是获取消息并显示加密和解密消息 我使用DES算法进行加密和解密 这是我的 HTML 文件
  • 检查 touchend 是否在拖动后出现

    我有一些代码可以更改表的类 在手机上 有时表格对于屏幕来说太宽 用户将拖动 滚动来查看内容 但是 当他们触摸并拖动表格时 每次拖动都会触发 touchend 如何测试触摸端是否是触摸拖动的结果 我尝试跟踪dragstart和dragend
  • React Native:加载图像后应用程序性能不佳

    加载图像似乎没有问题 但是加载完毕后就出现问题了 在我的应用程序中 我在整个游戏中一张一张地加载卡片图像 一旦我加载了 40 张卡片图像 整个应用程序就会变得很慢 它总是发生在第 40 个图像处 当我在第 40 个图像之后继续加载更多卡片图
  • 是否可以使用 javascript 测试用户的浏览器/操作系统是否支持给定类型的链接?

    是否可以使用 javascript 或其他任何东西 测试用户的操作系统 浏览器是否支持给定的 url 方案 例如 大多数仅使用网络邮件的用户计算机上未设置 mailto 是否有可能以某种方式捕获单击 mailto 链接的尝试并弹出比浏览器错
  • 如何在React中的Material-UI选择框中设置默认值?

    我在用选择框 https material ui com demos selects 来自材料用户界面 我想显示默认选择的 选择值 选项 但之后用户无法选择此选项
  • 通过 node-http-proxy 保留基于 cookie 的会话

    我有一个简单的基于 Express 的 Node js Web 服务器 用于开发 JavaScript 应用程序 我将服务器设置为使用 node http proxy 来代理应用程序向在不同域和端口上运行的 Jetty 服务器发出的 API
  • JavaScript 中数组的 HTML 数据列表值

    我有一个简单的程序 它必须从服务器上的文本文件中获取值 然后将数据列表填充为输入文本字段中的选择 为此 我想要采取的第一步是我想知道如何动态地将 JavaScript 数组用作数据列表选项 我的代码是
  • 如果链接包含特定文本,jQuery 将类添加到 href

    我的网站上的列表中有一些动态填充的链接 这些链接链接到文件 是否可以使用 jQuery 查看文件名是否以 pdf 结尾 并在 href 或类似的链接文本以 mp3 结尾时添加一个类 例如 我的列表中有以下链接 文件1 pdf 歌曲1 mp3
  • 使用 JS 合并具有相同值的相邻 HTML 表格单元格

    我已经为此苦苦挣扎了一段时间 我有一个根据一些 JSON 数据自动生成的表 该数据可能会有所不同 我想合并第一列中具有相同值的相邻单元格 例如此表中的 鱼 和 鸟 table tr td fish td td salmon td tr tr
  • 将数组排序为第一个最小值、第一个最大值、第二个最小值、第二个最大值等

    编写一个JS程序 返回一个数组 其中第一个元素是第一个最小值 第二个元素是第一个最大值 依此类推 该程序包含一个函数 该函数接受一个参数 一个数组 该函数根据要求返回数组 输入示例 array 2 4 7 1 3 8 9 预期输出 1 9
  • Select2 下拉列表动态添加、删除和刷新项目

    这让我发疯 为什么 Select2 不能在其页面上实现清晰的方法或示例如何在 Select2 上进行简单的 CRUD 操作 我有一个 select2 从 ajax 调用获取数据
  • Nodejs mysql 获取正确的时间戳格式

    我在用着mysqljs https github com mysqljs mysql得到结果后sql我变得不同TimeStamp格式如下 created at Sat Jul 16 2016 23 52 54 GMT 0430 IRDT 但
  • 需要有关 React Js 的帮助

    我是 React Js 新手 我的代码无法正常工作 请看下面 这是我的脚本文件Main jsx 该文件由 React 编译 输出放置在 dist 文件夹下的 main js 文件中 var react require react react
  • 在 Javascript 中减少/分组数组

    基于this https stackoverflow com a 40774906 3254598例如 我想以稍微不同的方式按对象进行分组 结果应该如下 key audi items make audi model r8 year 2012
  • 在 GWT 中,在任何主机页标记上添加事件处理程序

    我想为任何标签添加 MouseOver 事件处理程序 举个例子 我想为旧版 HTML 页面中的每个锚点页面添加事件处理程序 继GWT指南 http code google com webtoolkit doc 1 6 DevGuideUse

随机推荐

  • 修复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 的实现原