Giter Club home page Giter Club logo

blog's Introduction

Hi

blog's People

Contributors

dbwcooper avatar

Stargazers

 avatar

blog's Issues

npm 收集

  1. 权限问题
    执行 npm cache clean --force, 如果报错是因为一个包引发,尝试单独安装此包之后再安装其他包。
  2. 版本号说明
x: 代表最新版
^1.0.1:  会安装1.x.x
~1.0.1: 会安装 1.0.x 
 *:安装最新版。

记录 ssr 遇到的问题

  • 产生白屏的原因

    网站加载一个 html 模版页面,使用打包后的 js 生成对应的 html 标签,在这个过程中,js 加载的网络时间,js 生成 dom 的时间段页面都是白屏

  • 无法找到静态资源文件。

    在我们创建的 node server 中需要加入静态资源的路由识别。例如

    const uri = url.parse(req.url).pathname;
    if (uri === '/') {
      // 首页 渲染 html页面
      res.end(serverData);
    } else {
      //输出打包后的静态文件 index.js
      fs.createReadStream('./server/build/index.js').pipe(res);
    }

    tips: 默认为 package.json 所在目录作为根路径

  • require is not defined

    • 原因: 出现这种问题一般是 因为 服务端打包的文件规范为 commonjs, 会输出类似 module.exports = require(\"fs\"); 的代码,在浏览器端无法识别。
    • 解决方案: 在启动 server 时;server 端打包 react 文件通过 renderToString 能获取到 html 结构;同时 client 端打包 ReactDOM.hydrate ;在 server 端返回的 html 结构内引用 client 端打包的 js,对 html 结构进行事件绑定。
  • 如何处理数据

    • server 端可以用 getInitialProps (getStaticProps/getServerSideProps)方法获取异步数据,然后渲染到组件上,然后再通过 renderTotring 方法转换为 html结构
      • getInitialProps 只有在 page上才有此方法,目前猜测是因为后端路由的关系,一个路由对应一个页面,但是这个页面里面有多少组件这是未知的
    • client 端可以通过 useEffects 方法获取异步数据

    TODO:待手动实现 getInitialProps

  • document is not defined

    根本原因:
    style-loader 会将css打入js中,在js中生成style标签,使用 document.createElement("style”); 然而服务端没有 document 这个api。
    使用 mini-css-extract-plugin 插件将css代码从 js文件中抽离出来,
    不要在服务端打包的js中使用 document ;

  • 如何理解 getServerSideProps (getInitialProps)

    • async await

      • async 标志了当前函数的返回值为一个promise 对象;
      • await 等待一个表达式,如果是 函数hello 内部返回了一个promise对象,则 await阻塞接下来的代码执行。
      • async 默认会返回一个promise 对象。
        async function hello() {
          console.log('hello');
          const a = await fetch('https://www.baidu.com');
          console.log('a: ', a)
          console.log('end.')
        }
        hello()
        // hello (console.log输出)     
        // Promise {<pending>}  (如果没有返回值,async 函数默认会返回一个promise对象)
        并没有输出 a 和end. 因为被阻塞了。
        const b = async () => {
          return 1 + 1; // no
          // console.log('b'); no
          // return fetch('https://www.baidu.com'); yes
        }
        async function hello2() {
          console.log('hello');
          const a = await b;
          console.log('a: ', a)
          console.log('end.')
        }
        hello2()
        // hello
        // a: 2
        // end.
        // Promise {<pending>}  (如果没有返回值,async 函数默认会返回一个promise对象)
        
        /// await 这里不会阻塞后续表达式执行,因为 await后面不是一个promise对象

前端模块化

https://juejin.cn/post/6844903744518389768#heading-50

https://es6.ruanyifeng.com/#docs/module

https://es6.ruanyifeng.com/#docs/module-loader

iife 模块

  • iife (Immediately-Invoked Function Expression)立即执行函数 | 匿名函数自调用 (闭包)

    • 作用:数据是私有的,外部只能调用暴露的方法
    • 如下所示,iife_2.js 依赖 iife_1.js
    // case 1
    (function (window) {
      // 外部
      let prefix = 'https://www.bing.com';
    
      function innerCalc(query) {
        // 内部处理函数,外部无法访问
        return prefix + query;
      }
    
      function foo(query) {
        // 暴露给外部的方法
        console.log(`foo() ${prefix}`);
        return innerCalc(query);
      }
    
      // 暴露方法
      return (window.myModule = { foo });
    })(window);
    // case 2
    (function (module) {
      // 依赖外部的 module
      function bar() {
        const data = module.foo('?q=123');
    
        return `${data}&form=safari`;
      }
    
      module.bar = bar;
    })(myModule);
    // html 中
    <script type="text/javascript" src="/src/modules/iife_1.js"></script>
    <script type="text/javascript" src="/src/modules/iife_2.js"></script>
    <script type="text/javascript">
       console.log(myModule.bar());
     </script>
    • 导致的问题
      • 请求过多: 我们要依赖多个iife 模块,那就会有发送多个请求。
      • 依赖容易模糊:我们必须知道准确的依赖顺序,否则页面就会报错。

CommonJS

  • 概述

    • Nodejs 的模块规范,每个文件就是一个模块
    • 服务端:模块的加载是运行时同步加载
    • 浏览器端:浏览器不支持 commonjs 规范, commonjs 模块需要再浏览器端使用,需要提前编译打包。
  • 语法

    • 暴露模块:

      • module.exports = value
      • exports.xxx = value
    • 引入模块:

      • require(xxx) xxx 可以是文件路径,也可以是第三方 commonjs 包名
      • require 命令用于导出一个模块文件中的 exports 对象
  • 特点

    • commonjs 模块 也可用 .cjs 结尾

    • commonjs 模块在被 require 时,导出的对象是原始模块的值的一份拷贝。

      let count = 1;
      
      function addCount() {
        count++;
        return count;
      }
      
      exports.count = count;
      exports.addCount = addCount;
      
      // 外部模块执行 addCount 之后,导出的count 仍然为 1
    • 浏览器端如何使用?

      • 如果在浏览器端直接引入 commonjs 模块,页面会报错 require is not defined 。 因为浏览器端没有 require,module, module.exports 这三个对象。

      • 使用 browserify 这个库让浏览器可以支持 commonjs 模块 , 它会将所有模块 放入一个数组,当执行 require('xx') 时,就加载 xxx 对应 id 的 source文件,并返回 exports 对象。

        [
          {
            id: 1,
            source:
              "const data = require('./commonjs_1');\r\n\r\nfunction log() {\r\n  console.log(data.count);\r\n  console.log(data.addCount());\r\n}\r\n\r\nlog();\r\n\r\nsetTimeout(log, 100);",
            deps: { './commonjs_1': 2 },
            entry: true
          },
          {
            id: 2,
            source:
              'let count = 1;\r\n\r\nfunction addCount() {\r\n  count++;\r\n  return count;\r\n}\r\n\r\nexports.count = count;\r\nexports.addCount = addCount;',
            deps: {}
          }
        ];
  • commonjs 的问题

    • 无法异步加载文件
    • 一个文件只会导出一个 exports 对象,无法静态分析,意味着无法做 tree-shaking
  • 总结

    • commonjs 规范比较适用服务器端开发,因为一般 node 应用依赖的模块都会先被存在本地磁盘中,所以同步加载的模式比较块,不需要使用异步模式。
    • 但是浏览器环境就不一样了,页面依赖的文件很多来自服务端,需要异步加载处理。所以 commonjs 在浏览器环境不是很适用。

ES module

https://es6.ruanyifeng.com/#docs/module

  • 概述

    • ES6 模块的设计**是尽量的静态化,使得编译时就能确定模块的依赖关系,这样有很多好处,比如 tree-shaking

    • 判断当前代码是否在 es6 模块中

      if (this === undefined)
  • 语法

    • 模块导出

      export const name = 'Tom';
      export let age = 1;
      export var address = 'Cheng Du';
      export function sayHi(name){ console.log(`hi ${name}`)}
      export default function App(){ return 'App' } // 默认导出
      export { name, sayHi, App }
      export { name as Name } // 使用 as 重命名
      export { name as default } // name 作为 default 导出
      
      // 特殊用法
      export { default as AppIndex } from './es6_0';
      
      export * from './es6_0'; // 表示在当前文件中再导出 es6_0.js 文件中的所有模块
  • 模块引入

    import { firstName, lastName, year } from './index.js';
    import { firstName as name } from './index.js';
    import { default as Index } from './index.js';
    import * as all from './index.js'
  • import 异步加载模块

    import('./es6_0.js').then(() => {})
  • es6 模块的加载规则

    https://es6.ruanyifeng.com/#docs/module-loader

    • script 脚本异步访问 js
      • async 文件一旦下载完,浏览器会优先执行此脚本。暂停执行其他任务
      • defer 文件一旦下载完,会等待页面渲染完之后再执行,触发 domcontentloaded 事件
    • script 脚本访问 es6 模块
      • type = "module" === 支持 esmodule 并且添加了 defer 属性

Nodejs 的实际使用

https://es6.ruanyifeng.com/#docs/module-loader#Node-js-的模块加载方法

  • commonjs

    • .cjs 结尾的文件以 commonjs 规范解析。.cjs 结尾的文件只能用 commonjs 规范。
    • package.json 中 type 默认为 commonjs,表示当前项目中以js 结尾的文件以 commonjs 规范执行
  • esmodule

    • .mjs 结尾的文件以 esmodule 规范解析。.mjs 结尾的文件只能用 esmodule 规范。
    • 设置 package.json 中 type ="module"时,

package.json

  • type 属性 不同的值对应了什么意思

    • commonjs 当前项目中以js 结尾的文件以 commonjs 规范执行 。 默认
    • module 当前项目中以 js 结尾的文件以 esmodule规范执行。
    type: 'module' | 'commonjs'
  • main 字段

    • main 指定了模块的入口文件, 与type字段配合使用

    当前暴露的项目为 es module,入口为 dist/index.js

    // 包: es-test-module
    type: 'module',
    main: './dist/index.js' // 会被转换为 nod_modules/es-test-module/dist/index.js
  • exports 字段

    • exports 字段的优先级高于 main 字段

      exports 字段可以为 多个目录设置别名

      // 包名: es-test-modules
      type: 'module',
      exports: {
        submodule: "./dist/submodule/"
      }
      // 如果执行 import subFunc from 'es-test-modules/submodule/xxxx.js'
      // 会被转换为 import subFunc from 'node_modules/es-test-modules/dist/submodule/xxx.js'
    • exports 这种写法与 main 一致,但是优先级高于 main

      exports: {
        ".": "./dist/index.js"
      }
    • exports 这种写法只有支持 es6 语法的 nodejs 才认识,所以可以配合 main 一起,兼容旧版本和新版本的 nodejs

      main: './dist/index.js'
      exports: {
        ".": "./dist/index.js"
      }
  • commonjs 模块 与 es6 模块 互相引用

    • https://es6.ruanyifeng.com/#docs/module-loader#ES6-模块加载-CommonJS-模块

    • 使用 commonjs 模式无法加载 es6 模块的文件 ,因为 commonjs 是同步加载,es6 模块内部可以使用 await 关键字

      (async () => {
        await import('./my-app.mjs');
      })();
    • es6 模式可以加载 commonjs 模块的文件

      // 方式一 
      import name from 'commonjs-package'
      
      // 方式二 es6 模块中混用 commonjs 模块
      
      import { createRequire } from 'module';
      const require = createRequire(import.meta.url);
      const name = require('commonjs-package');
       

总结

现在是 2021 年了,编写 Node 程序时应该尽量使用 esmodule 模块规则来开发,esmodule 可以较好的兼容 commonjs 模块,反之则不行。

  • iife
    • 使用一个立即执行函数维持模块之间的关系,并使用闭包隐藏内部变量,这种方式到今天也仍然在使用。
    • 缺点是在模块太多时很容易出现依赖顺序问题。并且无法静态分析。
  • commonjs
    • Node 沿用至今的 模块处理方案,通过导出 exports 对象, 再使用 require(xxx) 方法导入实现。它与 esmodule 模块最大的不同有两点
      • 无法异步加载文件, 意味着 require('https://xxxxx,js') 是不行的。
      • require 的值是 exports 导出的值的 复制。
    • commonjs 模块无法兼容 es module 模块
  • es module
    • es6 出现的模块管理方案,Node 13.2 开始支持,通过 export 导出模块, import 引入模块实现.
    • 它与 commonjs 不同的是
      • import 语法可以异步加载文件,import('xx.js').then()
      • import 导出的值是 export 的值的引用

https://whimsical.com/Vv2rWdiwo8BTcToEF6Ki2s
image

实现一个 Promise

Promise 解决了哪些问题

  • 回调地狱常见于 Nodejs 的 Api fs.readFile fs.writeFile
  • 提供了常用的 并发模式 Api Promise.all

Promise 对象的主要特点

  • 状态

    • pending, Promise 对象的初始状态
    • fulfilled, Promise 对象 操作成功的状态
    • rejected, Promise 对象 操作失败的状态
  • then

    • 返回一个新的 Promise 对象
    • 可以链式调用
    • 值穿透特性
    • then 方法内返回新的的 Promise 对象
    // 返回 Promise, 可以链式调用then
    new Promise().then().then();
    // 值穿透
    new Promise((resolve, reject) => {
      resolve(1);
    })
      .then()
      .then((data) => {
        console.log(data); // 1
      });

编写 Promise 的步骤

  • 实现同步的 Promise

    class Promise {
      constructor(executor) {
        this.status = 'pending';
        this.value = undefined;
        this.reason = undefined;
    
        let resolve = (value) => {
          if (this.status === 'pending') {
            this.status = 'fulfilled';
            this.value = value;
          }
        }
        let reject = (value) => {
          if (this.status === 'pending') {
            this.status = 'rejected';
            this.reason = value;
          }
        }
    
        try {
          executor(resolve, reject)
        } catch ((e) => {
          reject(e)
        })
      }
    
      then = (onFulfilled, onRejected) => {
    
        if (this.status === 'fulfilled') {
          let x = onFulfilled(this.value);
          return x;
        }
        if (this.status === 'rejected') {
          let x = onFulfilled(this.value);
          return x;
        }
    
        if (this.status === 'pending') {
          // 异步
        }
      }
    }
  • 支持异步

    • 暂存 onFulfilled/onRejected 函数

    const FULFILLED = 'fulfilled';
    const REJECTED = 'rejected';
    const PENDING = 'pending';
    /**
    - 状态 pending|fulfilled|rejected
    - resolve/reject
    - then
    */
    class Promise {
      status: string;
      value: any; // resolve return
      reason: any; // reject return
    
      onResolveCallbacks: any; // 暂存异步之后需要执行的动作
      onRejectCallbacks: any;
    
      constructor(executor: any) {
        this.status = PENDING;
        this.value = null;
        this.onResolveCallbacks = [];
        this.onRejectCallbacks = [];
        let resolve = (value: any) => {
          if (this.status === PENDING) {
            this.status = FULFILLED;
            this.value = value;
            this.onResolveCallbacks.forEach((fn: any) => fn(this.value));
          }
        };
        let reject = (reason: any) => {
          if (this.status === PENDING) {
            this.status = FULFILLED;
            this.value = reason;
            this.onRejectCallbacks.forEach((fn: any) => fn(this.value));
          }
        };
    
        try {
          executor(resolve, reject);
        } catch (error) {
          reject(error);
        }
      }
    
      then = (onFulfilled?: any, onRejected?: any) => {
        onFulfilled =
          typeof onFulfilled === 'function' ? onFulfilled : (y: any) => y;
        onRejected =
          typeof onRejected === 'function' ? onRejected : (y: any) => y;
    
        if (this.status === FULFILLED) {
          let x = onFulfilled(this.value);
          return x;
        }
        if (this.status === REJECTED) {
          let x = onRejected(this.reason);
          return x;
        }
    
        if (this.status === PENDING) {
          this.onResolveCallbacks.push(() => {
            onFulfilled(this.value);
          });
          this.onResolveCallbacks.push(() => {
            onRejected(this.reason);
          });
        }
      };
    }
  • then 支持链式调用

    • 每个 then 方法都返回 promise 对象

    • 只需要改造 then 方法

    then = (onFulfilled?: any, onRejected?: any) => {
      onFulfilled =
        typeof onFulfilled === 'function' ? onFulfilled : (y: any) => y;
      onRejected =
        typeof onRejected === 'function'
          ? onRejected
          : () => {
              throw new Error('Uncaught');
            };
      if (this.status === FULFILLED) {
        return new Promise((resolve: any, reject: any) => {
          try {
            let x = onFulfilled(this.value);
            resolve(x);
          } catch (error) {
            reject(error);
          }
        });
      }
      if (this.status === REJECTED) {
        return new Promise((resolve: any, reject: any) => {
          try {
            let x = onRejected(this.reason);
            resolve(x);
          } catch (error) {
            reject(error);
          }
        });
      }
    
      if (this.status === PENDING) {
        return new Promise((resolve: any, reject: any) => {
          try {
            this.onResolveCallbacks.push(() => {
              try {
                let x = onFulfilled(this.value);
                resolve(x);
              } catch (error) {
                reject(error);
              }
            });
            this.onRejectCallbacks.push(() => {
              try {
                let x = onRejected(this.reason);
                resolve(x);
              } catch (error) {
                reject(error);
              }
            });
          } catch (error) {
            reject(error);
          }
        });
      }
    };
  • then 方法支持内部返回 promise

    • 即是 onFulfilled onRejected 方法返回 Promise

    • 内部返回的 promise 执行之后,再继续执行外部 then 方法

    • 这里将 then 的返回值抽出来,用一个函数包装内部逻辑
    let x = onFulfilled(this.value); // 如果 x 返回 promise 对象
    
    let resolvePromise = (promise, x, resolve, reject) => {
      // 判断x 是否是 promsie
      try {
        if (
          typeof x !== null &&
          (typeof x === 'function' || typeof x === 'object')
        ) {
          let then = x.then;
          if (typeof then === 'function') {
            then.call(
              promise,
              (value) => {
                // resolve(x)
                // 如果 then 仍然是一个promise
                resolvePromise(promise, value, resolve, reject);
              },
              (e) => {
                reject(e);
              }
            );
          }
        } else {
          return resolve(x);
        }
      } catch (e) {
        reject(e);
      }
    };

Git 收集

  1. 修改 commit 信息,使用命令 git commit --amend, vi 修改, 只修改最后一条commit 信息。git 撤消

  2. 修改 commit 信息 (记录 已push), 使用命令 git rebase -i head~5

  3. 取消 git commit 记录,但是保留代码: git reset --soft head~1, 1 表示commit 条数。

  4. 取消 git commit 记录, 不保留代码: ` git reset --hard commitId ```。

  5. window 下设置 git 大小写敏感 git config core.ignorecase false

  6. 删除已经被放入 git 历史版本的文件或文件夹
    git rm --cached plugins/index.js
    git commit -m "chore: remove a file in git history"

  7. 合并commit 记录(只能合并相邻的commits)
    git rebase -i hash值 , 然后将pick 选为 squash.
    squash 会将当前commit 合并到上一个commit。
    保存退出(wq)之后,回提示你是否需要更新commit 信息
    tips: 如果遇到合并冲突, 先解决冲突,然后git add 之后执行 git rebase --continue

  8. 更新 commit的用户名和地址
    如果已经commit了, 可以直接执行 git commit --amend --author="dbwcooper <[email protected]>"
    如果未commit
    git config --local user.name "dbwcooper"
    git config --local user.email "[email protected]"

div 如何自适应高度

背景: 左右布局的div 块,左侧为菜单栏,右侧为内容块,如何保证左右块同高?

Web页面 文件下载

文件下载

常见处理文件下载方式有两种

  • 新开浏览器tab下载

    后端返回格式 如何处理 优点 缺点
    文件流 window.open 简单 新开tab页面会有一瞬间的空白;ie下可能拦截; 无法处理接口异常;
    文件链接 window.open 简单 新开tab页面会有一瞬间的空白;ie下可能拦截; 无法处理接口异常;
  • 当前页面下载

    后端返回格式 如何处理 优点 缺点
    文件流 iframe 能处理接口异常;有浏览器自带的进度条显示 交互友好
    文件链接 iframe 能处理接口异常;有浏览器自带的进度条显示 交互友好
    文件流 a 简单,ie 不兼容
    文件链接 a 简单,ie 不兼容
  • 使用 a 标签

    • 要求
      • api 请求方式为 get, 并返回文件流 | 或一个具体的文件
      • api 的地址与当前页面的地址的 域名 必须一致! (跨域)
      • api 地址必须以 https 开头 见 MDN 安全策略
  • 缺点

    • 无法得知下载文件的进度
    • ie下 a 标签没有 download 属性,点击a 标签会直接打开文件 而不是下载。
  • 代码

    const A_ID = 'd_a_key_2020';
    const getADownload = (blob: Blob | string, fileName: string) => {
      const url = typeof blob === 'string' ? blob : window.URL.createObjectURL(blob);
      let aDom = document.getElementById(A_ID) as HTMLAnchorElement;
      if (!aDom) {
        aDom = document.createElement('a');
        document.body.appendChild(aDom);
      }
      aDom.id = A_ID;
      aDom.style.display = 'none';
      aDom.href = url;
      aDom.download = fileName; // ie dont work, download 属性添加之后,点击a 标签不会直接打开文件,而是下载这个文件。
      aDom.click();
      aDom.onload = () => {
        console.log('onload:')
      }
    };
  • 使用 iframe 标签 (最靠谱)

    • 要求

    • 代码

      const getIframeDownload = (api: string, onComplete?: (str: string | null) => void) => {
        let iframeDom = document.getElementById(IFRAME_ID) as HTMLIFrameElement;
        if (iframeDom) {
          document.body.removeChild(iframeDom);
        }
        iframeDom = document.createElement('iframe');
        iframeDom.style.display = 'none';
        iframeDom.id = IFRAME_ID;
        iframeDom.src = api;
        // trigger download;
        iframeDom.onload = () => {
          // iframe 的地址与 当前页面的地址必须在相同的域名下! 才能拿到 contentDocument
          const iframeDoc = iframeDom.contentDocument;
          if (iframeDoc) {
            if (
              iframeDoc.readyState === 'complete' ||
              iframeDoc.readyState === 'interactive'
            ) {
              // 文件下载出错时;后端会返回一串字符,一般是一串json, 此时responseText有值
              // 文件正常下载时; responseText === null
              if (typeof onComplete === 'function') {
                const responseText = iframeDoc.body.textContent || null ;
                onComplete(responseText)
              }
            }
          }
        };
        document.body.appendChild(iframeDom);
      };
  • POST 方式下载

    代码

    // saveAs, 建议使用 https://github.com/eligrey/FileSaver.js/
    export function downloadUrlFile(url: string) {
      const xhr = new XMLHttpRequest();
      xhr.open('GET', url, true);
      xhr.responseType = 'blob';
      // xhr.setRequestHeader('Authorization', 'Basic a2VybWl0Omtlcm1pdA==');
      xhr.onload = () => {
        if (xhr.status === 200) {
          // 获取图片blob数据并保存
          // saveAs(xhr.response, 'abc.xlsx');
        }
      };
     
      xhr.send();
    }

光标快捷操作

mac终端的光标操作

删除快捷键

删除 光标前的命令

  1. control + h === backspace 回退键
  2. control + w 删除光标前的一个单词
    • 例如执行git操作写错了 status, git stsau 使用 control + w 可以快速删除 ststau
    • 简单说: git ststau -> git
  3. control + u 删除光标之前到行首 的所有字符串
    • git ststau -> 空白

删除 光标后的命令

  1. control + k 删除光标后的 所有字符

删除 的其他命令

  1. control + l 清屏 相当于输入clear

光标移动

  1. control + a 光标向左移动一位
  2. control + e 光标向右移动一位
  3. option + < 光标 单词间移动 向左
  4. option + > 光标 单词间移动 向右
  5. option + shift + \ 光标在 {} 上下移动
  6. command + up/down 光标移动到文件头/文件尾

vscode 光标操作

https://geek-docs.com/vscode/vscode-tutorials/vscode-keyboard.html#i

  1. option + command + . 格式化单行代码

  2. control + ` 打开vscode 的terminal

  3. 在word 上加特殊符号 control + command + 空格 左上角 设置 -> 项目符号 + 星星 -> 拖动符号✅到pages 文档内

删除
  1. command + shift + k 删除选中行

编辑

  1. command + enter 在光标所在行的下一行新增一行
  2. command + shift + enter 在光标所在行的上一行新增一行

撤销 光标移动

  1. command + u

合并代码行

  1. ctrl + j 合并两行代码为一行代码

关闭当前窗口内打开的所有 tab

  1. command + k + w

获取元素距离位置

问题

  • 使用 getBoundingClientRect 获取元素位置时,top 和 left 值 获取不准确。

场景

  • 切换页面A -> 页面B,不刷新页面只重新挂载同一组件。
  • componentDidMount 内使用 getBoundingClientRect 获取元素相对文档的x y轴坐标

如何重现

  • 页面 A 滑动滚动条,使得 window.scrollY 不为0 ;(最好滑动滚动条到页面底部)。
  • 此时切换页面B,页面不刷新,但是滚动条会默认滑动到页面顶部。
  • 在组件的 componentDidMount 内使用 getBoundingClientRect 方法获取到的 height top; 此时的 height top 不是相对于页面B的文档,而是相对于页面A文档的最后状态。

解决方案

  • 在组件重新挂载时,强制将滚动条滚动到页面顶部。
import React from 'react';

const scrollToTop = () => {
  const sTop =
    document.documentElement.scrollTop ||
    document.body.scrollTop ||
    document.scrollingElement!.scrollTop;
  if (sTop > 0) {
    window.requestAnimationFrame(scrollToTop);
    window.scrollTo(0, sTop - sTop / 8);
  }
};

class App extends React.Component<
  {},
  {
    headerTop: number;
    top: number;
  }
> {
  state = {
    headerTop: 0,
    top: 0,
  };

  siderRef = React.createRef<HTMLDivElement>();

  constructor(props) {
    super(props);
    scrollToTop();
  }

  componentDidMount() {
    this.setHeaderTop();
    document.addEventListener('scroll', () => {
      const { headerTop } = this.state;
      // const { height } = this.siderRef.current!.getBoundingClientRect();
      // const {
      //   height: titleHeight,
      // } = this.siderRef
      //   .current!.getElementsByClassName('stellr-dashboard-sider-title')[0]
      //   .getBoundingClientRect();
      // const {
      //   height: menuHeight,
      // } = this.siderRef
      //   .current!.getElementsByClassName('stellr-dashboard-menu')[0]
      //   .getBoundingClientRect();

      // 找到 最大滚动距离
      const maxScrollTop = 300;
      // 找到已经滚动的高度
      const tempTop =
        window.scrollY - headerTop > 0 ? window.scrollY - headerTop : 0;
      // 计算 menu 和 title在sider 内应该滑动的距离
      const newTop = tempTop > maxScrollTop ? maxScrollTop : tempTop;

      window.requestAnimationFrame(() => {
        this.setState({ top: newTop });
      });
    });
  }

  componentWillUnmount() {
    document.removeEventListener('scroll', () => {});
  }

  setHeaderTop = () => {
    if (this.siderRef && this.siderRef.current) {
      // 在滚动条滚动时, top 和 left 的值会根据滚动条的变化而变化。
      // 如果页面不刷新 页面A -> 页面 B; 重新挂载此组件,getBoundingClientRect第一次获取到的 top, left 值会受滚动条的影响。
      // 使用 offsetTop可替换
      const { top } = this.siderRef.current!.getBoundingClientRect();
      this.setState({
        headerTop: top, // 当前div的左上角距离整个 文档的高度
        // siderHeight: 0,
      });
    }
  };

  render() {
    const { top } = this.state;
    return (
      <div
        style={{ height: '100%', display: 'flex', flex: 1 }}
        ref={this.siderRef}
      >
        <div style={{ position: 'absolute', left: 0, top, width: '100%' }}>
          滚动块
        </div>
      </div>
    );
  }
}
export default App;

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.