代码之家  ›  专栏  ›  技术社区  ›  Ruslan Zhomir

Next中的变量。JS项目在未赋值的情况下更改了其值

  •  0
  • Ruslan Zhomir  · 技术社区  · 3 年前

    我遇到了一些非常奇怪的行为。我有一个更大的项目,但为了演示的目的,下面是简单的Next。JS应用程序就足够了。

    复制步骤:

    1. 创建一个简单的Next。JS项目:

      npx create-next-app my-next-app --ts

    2. 将以下2个文件添加到项目中:

    // ./componens/logs.ts
    
    export function createLogger(options: { name: string }) {
      const logIdObj = { logId: 0 };
      function vivify(o: { logId: number }) {
        let n = 0;
        Object.defineProperty(o, 'logId', {
          get: () => {
            console.warn(`Getting logId for logger ${options.name}: ${n}`);
            return n;
          },
          set: (v: number) => {
            console.warn(`Setting logId for logger ${options.name}: ${v}`);
            n = v;
          },
        });
      }
      vivify(logIdObj);
    
      return (message: string) => {
        logIdObj.logId += 1;
        console.log(`${logIdObj.logId}. ${message}`);
      };
    }
    
    // ./componens/Chat.tsx
    
    import { useEffect } from 'react';
    import { createLogger } from './logs';
    
    const log = createLogger({ name: 'Chat' });
    
    export default function Chat(): JSX.Element {
      log('render');
    
      useEffect(() => {
        log('useEffect');
      }, []);
    
      return <div>Some content</div>;
    }
    
    1. 修改主页面文件 ./pages/index.tsx 这样地:
    import type { NextPage } from 'next'
    import Chat from '../components/Chat'
    
    const Home: NextPage = () => {
      return <>
          <Chat />
          <h1>Welcome</h1>
        </>
    }
    
    export default Home
    
    1. 以开发模式运行项目: npm run dev

    2. 打开浏览器,导航到 localhost:3000 看看您将在控制台中得到什么:

    enter image description here

    如中所示 ./componens/logs.ts 对的访问 logId 的属性 logIdObj 对象被有意地包装在getter-setter拦截器中,以记录对它的任何类型的访问,但根据日志,值在未被访问的情况下被更改。

    这是我的环境:

    Distributor ID: Debian
    Description:    Debian GNU/Linux 10 (buster)
    Release:        10
    Codename:       buster
    node: v14.17.0
    npm: 7.22.0
    

    和我的 package.json :

    {
      "name": "my-next-app",
      "version": "0.1.0",
      "private": true,
      "scripts": {
        "dev": "next dev",
        "build": "next build",
        "start": "next start",
        "lint": "next lint"
      },
      "dependencies": {
        "next": "11.1.2",
        "react": "17.0.2",
        "react-dom": "17.0.2"
      },
      "devDependencies": {
        "@types/react": "17.0.30",
        "eslint": "7.32.0",
        "eslint-config-next": "11.1.2",
        "typescript": "4.4.4"
      }
    }
    

    我想这可能与内部的Next有关。JS逻辑,但无论如何都不应该这样工作。

    一如既往,我们将不胜感激。


    使现代化 :

    虽然是第一次回复(感谢 Ben )对问题的原因没有给出明确的答案,它给出了可能出错的线索。所以我修改了 ./componens/logs.ts 这样地:

    const allLogs = [] as { message: string, logId: number, time: number }[];
    if (typeof window !== 'undefined') {
      // @ts-ignore
      window.allLogs = allLogs;
    }
    
    export function createLogger(options: { name: string }) {
      let logId = 0;
    
      return (message: string) => {
        logId += 1;
        allLogs.push({ message, logId, time: Date.now() });
        console.log(`${logId}. ${message}`);
      };
    }
    

    现在我的结果是这样的: enter image description here

    现在我们可以清楚地看到,渲染实际上发生了两次 console.log() 并不总是产生实际的日志。尽管如此,原因尚不清楚。

    有什么解释吗?

    0 回复  |  直到 3 年前
        1
  •  2
  •   Ruslan Zhomir    3 年前

    实际原因是React在 StrictMode :

    从React 17开始,React自动修改控制台方法,如 console.log() 以使对生命周期函数的第二次调用中的日志静音。然而,在某些情况下,它可能会导致不期望的行为。。。

    有关进一步的解释,请参阅 https://reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects

        2
  •  1
  •   Ben    3 年前

    你说得对。如果您更改:

    set: (v: number) => {        
      console.log(`Setting logId for logger ${options.name}: ${v}`);
      n = v;
    },
    

    set: (v: number) => {        
      alert(`Setting logId for logger ${options.name}: ${v}`);
      n = v;
    },
    

    当然,你会看到二传手正在被召唤。

    我不知道根本原因——这可能是一个值得在下一次回购中作为问题提出的错误,或者至少有人能够解释原因。