养成习惯使用useMome了吗?我劝你别用啊

1,193 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第3天,点击查看活动详情

记录一次bug调试的漫长旅程。

bug体现,切换屏幕大小时,屏幕宽度到指定大小1000px时,会造成组件刷新,由于组件中有视频,导致播放中视频停止,并回到开头

故事的起源是一个老项目针对少数用户维护(老,是相对的,不是真的老,技术是新的技术,架构比较老而已)。由于升级新的架构,有点风险,所有保留了老架构的的部分业务。 在日常中,人们也希望使用一些新的东西,有些新的组件想在老架构里使用,有幸由我进行了适配(control c v),由于老的项目的数据结构和新的数据结构存在略微差异,所以,我采用了适配器的设计模式(哈哈哈),有个中间层,把老数据格式包裹一下,包装成新组件可识别的格式,因此写下了类似下面的代码,因为出现了bug,所有重新读了一下代码,发现问题颇多。

import { useEffect, useState } from "react";
import useMedia from "use-media";
import "./App.css";
// 这数据是来自网络请求,这里模拟一下
var myData = {
  title: "题目",
  content: "内容",
};
var mobileData = {
  title: "题目1",
  content: "内容1",
};
// 这个是老架构里为了适配移动端
function App() {
  const isWide = useMedia({ minWidth: 1000 });

  console.log("APP更新了", isWide);
  let data = myData;
  if (window.isMock && isWide) {
    // 某种业务情况下,这里会执行,但是当前业务不会执行,移动端和pc使用同一份数据
    data = mobileData;
  }
  return (
    <div className="App">
      <header className="App-header">{renderData(data)}</header>
    </div>
  );
}

export default App;

function Content(props) {
  const { contentObj, source } = props;
  const [renderSource,setRenderSource]=useState(source);
  console.log("content更新了");
  useEffect(()=>{
    setRenderSource({
      fruits:source.fruits.map(item=>{
        item.id=Math.random();
        return item;
      })
    })
  },[source])
  return (
    <div>
      <h1>{contentObj.title}</h1>
      <p>{contentObj.content}</p>
      {renderSource.fruits.map((item) => {
        return (
          <div key={item.type} id={item.id}>{item.type + "的价格为:" + item.price}</div>
        );
      })}
    </div>
  );
}
// renderData里面逻辑是我新加的,目的就是为了使用新组件Content,
// 由于Content需要的属性是固定格式,新的架构里这样用的,不可更改,所以我包裹了一下,
// 其实不考虑上下文时,个人感觉这个逻辑没问题,因为是无状态,组件属性不更新,组件不更新,
// 所以 ... 哈哈哈
function RenderData(props) {
  console.log("renderData 更新了");
  const { title, content } = props;
  const contentObj = { title, content };
  // 这个是个插件配置,业务需要,写死即可
  const source = {
    fruits: [
      {
        price: 22,
        type: "苹果1",
      },
      {
        price: 30,
        type: "香蕉1",
      },
      {
        price: 100,
        type: "葡萄1",
      },
    ],
  };
  return <Content contentObj={contentObj} source={source}></Content>;
}
function renderData(data) {
  return <RenderData {...data}></RenderData>;
}

这段代码在线上效果就是这样

20220804105648.gif 20220804105600.gif

排查问题时排查了很久。因为忽略了useMedia这个hooks,惭愧啊

image.png

有几次定位到问题出在这里了,但是麻木的以为这里没执行,data 没变,react就不更新了,react 都替我处理了。我想react内心一定是气炸了,每次赋值都是新对象,并且你的render(data)本身就是动态的东西,每次都不一样。你不改,我就一直更新。 试着做如下更改,把动态渲染的东西用useMome缓存起来,react再更新时,假设data没变,这个值就一直复用之前的,假设data变了,这个函数重新执行一下得到一个新值,道理都懂,擦,就是不好好干

// 这个是老架构里为了适配移动端
function App() {
  const isWide = useMedia({ minWidth: 1000 });

  console.log("APP更新了", isWide);
  let data = myData;
  if (window.isMock && isWide) {
    // 某种业务情况下,这里会执行,但是当前业务不会执行,移动端和pc使用同一份数据
    data = mobileData;
  }
  +   const renderDataDom=useMemo(()=>renderData(data),[data])
  return (
    <div className="App">
     - <header className="App-header">{renderData(data)}</header>
     + <header className="App-header">{renderDataDom}</header>
    </div>
  );
}

20220804105942.gif

20220804110033.gif

得,破案了,提前下班吧。 问题还没完啊,因为老的组件也是在这种条件下生存的,老组件为啥没问题呢?而且这个老架构已经运行很久,这个App,在架构里也是App级别的,不能随便改,容易带来风险,如果要改,就要充分测试(项目太大,鬼才愿意测试)。还是要在自己写的适配组件上入手,解决这个问题,回过头冷静看一下。自己的代码,确实有漏洞啊,

function RenderData(props) {
  console.log("renderData 更新了");
  const { title, content } = props;
  const contentObj = useMemo(()=>{
    return { title, content };
  },[title,content]);
  // const contentObj={ title, content }
  const source =useMemo(()=> {
    return {
      fruits: [
        {
          price: 22,
          type: "苹果1",
        },
        {
          price: 30,
          type: "香蕉1",
        },
        {
          price: 100,
          type: "葡萄1",
        },
      ],
    }
  },[])
  // const source = {
  //     fruits: [
  //       {
  //         price: 22,
  //         type: "苹果1",
  //       },
  //       {
  //         price: 30,
  //         type: "香蕉1",
  //       },
  //       {
  //         price: 100,
  //         type: "葡萄1",
  //       },
  //     ],
  // };
  return <Content contentObj={contentObj} source={source}></Content>;
}

这样一改,虽然没能阻止祖先级组件的雪崩渲染,但是避免了子组件的无脑渲染。所有引用类型,不用hooks包裹,都可能给子孙带来灾难, 所以爱护地球从你我做起

20220804110220.gif

20220804105409.gif