侧边栏壁纸
博主头像
M酷博主等级

一帆风顺 ⛵️⛵️⛵️

  • 累计撰写 45 篇文章
  • 累计创建 40 个标签
  • 累计收到 464 条评论

目 录CONTENT

文章目录

React18 + Vite3 + TS 项目常见问题

M酷
2023-01-13 / 1 评论 / 14 点赞 / 8,823 阅读 / 10,081 字 / 正在检测是否收录...
广告 广告

1、react 中 a 标签链接报错:Warning: A future version of React will block javascript: URLs as a security precaution.

当项目中 a 标签的 href 值为 javascript:; 或者 javascript:void() 时,在编译中会报错,具体报错信息如下:
Warning: A future version of React will block javascript: URLs as a security precaution.
Use event handlers instead if you can. If you need to generate unsafe HTML try using dangerouslySetInnerHTML instead. React was passed “javascript:;”.

解决方案href 后面不用赋值,但要给点击事件加上 preventDefault,即:

<a onClick={(e) => e.preventDefault()}>点我</a>
<!-- 如果需要判断href,像下面这样写可以 -->
<a href="link ? link : '#'" onClick={(e) => !link && e.preventDefault()}>点我</a>

2、‘XXX’ implicitly has an ‘any’ type

TS 项目在接收参数时使用了参数解构,遇到了如下警告提示: Binding element ‘XXX’ implicitly has an ‘any’ type

ts_err

遇到这种解构参数识别不到类型的,只需要给该参数补上类型声明即可。
rowTyperow 对象的类型声明,当然也可以直接声明为 any

const text= ({ row }: { row: rowType })=>{
	// ...
}

3、typescript 报错 Cannot find name “__dirname”

这通常是由于 tsconfig.js 配置的问题

1). 将 node 添加到 tsconfig.json 文件中的 compilerOptions.types

{
 "compilerOptions": {
  ...
  "types": [
    "node"
  ]
  ...
  }
}

2). 安装@types/node

yarn add @types/node --save-dev

4、动态加载/切换组件

使用 React.lazyReact.Suspense 来加载即可,这样也方便后面配合 webpacksplitchunks 做代码拆分。

import React, { Suspense, lazy, useState, useCallback } from "react";
import "./styles.css";

export default function App() {
  const [DynamicModule, setDynamicModule] = useState("div");

  const onClick = useCallback((e) => {
    let newName = e.target.dataset.name;
    setDynamicModule(
      lazy(() => import(/* webpackMode: "eager" */ `../modules/${newName}`))
    );
  }, []);

  return (
    <div className="App">
      <button data-name="moduleA" onClick={onClick}>
        按钮 A
      </button>
      <button data-name="moduleB" onClick={onClick}>
        按钮 B
      </button>
      <div>
        内容区
        <div>
          <Suspense fallback={<div>"loading"</div>}>
            <DynamicModule />
          </Suspense>
        </div>
      </div>
    </div>
  );

5、hooks 中使用 await

useEffect 的回调参数返回的是一个清除副作用的函数。因此无法返回 Promise,更无法使用 async/await,那我们有什么办法来让它支持 async/await 呢?

1). 方法一(推荐):useEffect 中异步函数采用 IIFE 写法( Immediately Invoked Function Expression 即立即调用的函数式表达式)

const MyFunctionnalComponent: React.FC = props => {
  useEffect(() => {
    // Using an IIFE
    (async function anyNameFunction() {
      await loadContent();
    })();
  }, []);
  return <div></div>;
};

2). 方法二:此时可以选择再包装一层 async 函数,置于 useEffect 的回调函数中,变相使用 async/await

const fetchMyAPI =async()=> {
  let response = await fetch('api/data')
  response = await res.json()
  dataSet(response)
}
useEffect(() => {
  fetchMyAPI();
}, []);

3). 方法三:在内部创建一个异步函数,等待 getData(1) 结果,然后调用 setData():

useEffect(() => {
  const fetchMyAPI = async () =>{
    const data = await getData(1);
    setData(data)
  }
  fetchMyAPI();
}, []);

6、TypeScript 项目报错 Cannot use import statement outside a module, Unknown file extension “.ts“

tsconfig_err

解决方法:只需要在 tsconfig.js 里添加 "module": "commonjs" 即可。

7、useEffect 中清除 setTimeout 等副作用

useEffect 函数体中是当前的副作用,它也可以返回一个函数用来清除当前的副作用。

useEffect(() => {
  let timer = setTimeout(() => {}, 1000)
  return ()=> clearTimeout(timer)
}, [])

8、从枚举类型中提取 key 或 value 组成的联合类型(枚举类型转联合类型)

有时候,我们想通过之前定义的枚举类型中提取出可用的联合类型,比如提取其中的 key 或者 value 组成的联合类型,以下是相关用法示例:

enum EnumFruit = {
	orange:  'Orange',
  apple:  'Apple' ,
  banana: 'Banana'
}

// 获取枚举类型的 key 组成的联合类型
type KeyUnion = typeof EnumFruit // 'orange' | 'apple' | 'banana'
// 获取枚举类型的 value 组成的联合类型
type ValueUnion = `${EnumFruit}` // 'Orange' | 'Apple' | 'Banana'

9、interface 中的联合类型验证不通过

当我们在 interface 中定义了一个类型为联合类型的属性,在赋值的时候就会出现这类错误,如下示例:

interface Fruit {
	fruit: 'Orange' | 'Apple' | 'Banana',
    count: number
}
const fruitList:Fruit[] = [{fruit: 'Apple', count: 6 }]
// Type 'string' is not assignable to type '"Orange" | "Apple" | "Banana"'

到这种情况可以考虑使用 对象字面量 以及 typeofkeyof 推断相关类型:

const FruitObj = {
  orange:  'Orange',
  apple:  'Apple' ,
  banana: 'Banana'
} as const  // 一定要加 as const,不然没用

interface Fruit {
  fruit: typeof FruitObj[keyof typeof FruitObj]
  count: number
}

const fruitList:Fruit[] = [{fruit: 'Apple', count: 6 }]

参考文章:

10、Type ‘undefined’ cannot be used as an index type

当我们在 TS 中使用中括号动态访问对象中的某个属性或者数组中的某个值时,可能会提示这个错误。主要是由于 TS 无法确定这个动态属性的值导致的。如下例子所示:

// 👇️ key is optional (could be undefined)
const obj1: { key?: string } = {
  key: 'name',
};

const obj2 = {
  name: 'James Doe',
};

// ⛔️ Error: Type 'undefined' cannot be
// used as an index type.ts(2538)
const result = obj2[obj1.key];

因此,我们需要对这个动态属性添加类型守卫(type guard),在使用前判断一下,如果不为 undefined 才使用,并且使用 as 把它断言为对象的 key,代码如下:

if (obj1.key != undefined) {
  result = obj2[obj1.key as keyof typeof obj2];
}
console.log(result); // 👉️ "James Doe"

参考:Type ‘undefined’ cannot be used as an index type in TS

11、‘React’ refers to a UMD global, but the current file is a module. Consider adding an import instead

出现这个错误应该是 ts 的版本和 React 有冲突,可以通过配置 tsconfig.json 文件中的 jsx 属性解决。相关 issue

"jsx": "preserve",

12、路由懒加载的场景下,项目发版后导致没有刷新的用户出现js文件加载失败: ChunkLoadError: Loading chunk 42 failed.

这种情况主要是由于浏览器缓存导致的,一般为了避开缓存的影响,我们打包出来的文件名都会带上唯一的 hash 值,但是当我们发版本后,那些没有刷新页面的用户在切换页面的时候,就会出现加载 js 出错的问题,而且一般发生在单页面的场景下。

解决方法:

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { error: null, errorInfo: null };
  }
  
  componentDidCatch(error, errorInfo) {
    // Catch errors in any components below and re-render with error message
    this.setState({
      error: error,
      errorInfo: errorInfo
    })
    // You can also log error messages to an error reporting service here
  }
  
  render() {
    if (this.state.errorInfo) {
      // Error path
      return (
        <div>
          <h2>Something went wrong.</h2>
          <details style={{ whiteSpace: 'pre-wrap' }}>
            {this.state.error && this.state.error.toString()}
            <br />
            {this.state.errorInfo.componentStack}
          </details>
        </div>
      );
    }
    // Normally, just render children
    return this.props.children;
  }  
}

class BuggyCounter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { counter: 0 };
    this.handleClick = this.handleClick.bind(this);
  }
  
  handleClick() {
    this.setState(({counter}) => ({
      counter: counter + 1
    }));
  }
  
  render() {
    if (this.state.counter === 5) {
      // Simulate a JS error
      throw new Error('I crashed!');
    }
    return <h1 onClick={this.handleClick}>{this.state.counter}</h1>;
  }
}

function App() {
  return (
    <div>
      <p>
        <b>
          This is an example of error boundaries in React 16.
          <br /><br />
          Click on the numbers to increase the counters.
          <br />
          The counter is programmed to throw when it reaches 5. This simulates a JavaScript error in a component.
        </b>
      </p>
      <hr />
      <ErrorBoundary>
        <p>These two counters are inside the same error boundary. If one crashes, the error boundary will replace both of them.</p>
        <BuggyCounter />
        <BuggyCounter />
      </ErrorBoundary>
      <hr />
      <p>These two counters are each inside of their own error boundary. So if one crashes, the other is not affected.</p>
      <ErrorBoundary><BuggyCounter /></ErrorBoundary>
      <ErrorBoundary><BuggyCounter /></ErrorBoundary>
    </div>
  );
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);

13、The operand of a ‘delete’ operator must be optional

当我们在 TS 中使用 delete 删除对象的属性时,可能会遇到这个错误。

interface Person {
  name: string
  age: number
}
const person:Person = { name:'Jack', age: 20 }
delete.name // The operand of a 'delete' operator must be optional

strictNullChecks 中使用 delete 运算符时,操作数现在必须为 anyunknownnever 或为可选(因为它在类型中包含 undefined)。我们只需要确保要删除的那个属性为可选类型即可,如下:

interface Person {
  name?: string
  age: number
}
const person:Person = { name:'Jack', age: 20 }
delete.name

14、svg 压缩后,viewbox 属性丢失,导致图标展示异常

在使用 @svgr/webpack 插件压缩 svg 之后,发现线上出现图标尺寸展示异常,经过检查,发现是 svg 的 viewbox 属性被移除导致的,此时只需要修改插件的配置即可。相关 issue

{
  test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
  exclude: /node_modules/,
  use: [
    {
      loader: '@svgr/webpack',
      options: {
        svgoConfig: {
          plugins: [
            { name: 'removeViewBox', active: false } // 保留 viewbox 属性,避免图标展示异常
          ]
        }
      }
    }
  ]
}

15、react 中渲染 html

react 中需要通过 dangerouslySetInnerHTML 这个属性来渲染 html 内容

  • dangerouslySetInnerHTMLReact 标签的一个属性,类似于 angularng-bind
  • 有 2 个 {{}},第一个 {} 代表 jsx 语法开始,第二个是代表 dangerouslySetInnerHTML 接收的是一个对象键值对。
  • 既可以插入 DOM ,又可以插入字符串。
<div dangerouslySetInnerHTML={{__html: processResponsiveText(content)}}></div>

16、The expected type comes from property ‘onClick’ which is declared here on type xxx

出现这个错误主要是由于 TS 无法推断出当前 event 的类型导致的,我们只需要为 event 对象指定相应的类型即可,常用的可能会有 React.MouseEvent<HTMLDivElement>React.MouseEvent<HTMLButtonElement> ,如下例子使用到了 React.MouseEvent<HTMLDivElement> 这个类型:

参考:Typescript and React: What is the proper type for an event handler that uses destructuring?

  const handleClick = ({
    currentTarget
  }: React.MouseEvent<HTMLDivElement>) => {
    setAnchorEl(currentTarget)
  }

<div onClick={handleClick}>点击我</div>

17、Warning: validateDOMNesting(…): ‘td’ cannot appear as a child of ‘div’

在使用 MUI 的 TablePagination 组件的时候,我遇到了这个问题,提示说的是 td 不应该作为 div 的子元素,这个组件内部使用了 datagrid 这个插件,他默认以 div 渲染,但是这个插件默认以 table 来渲染,最终 TS 会提示这个错误。

经过查阅文档,发现该组件提供了一个 component 属性,可以指定它以何种元素来渲染。如下代码:

<div className="w-full flex justify-end">
  <TablePagination
    sx={{ border: 'none' }}
    rowsPerPageOptions={[]}
    component="div"
    colSpan={3}
    count={total}
    labelDisplayedRows={() => null}
    rowsPerPage={rowsPerPage}
    page={page}
    onPageChange={handleChangePage}
    ActionsComponent={TablePaginationActions}
    />
</div>

18、React version not specified in eslint-plugin-react settings

eslintrc.js 或者 .eslintrc 文件中添加如下配置项,让它自行检查版本信息即可:

{
  "settings": {
    "react": {
      "version": "detect"
    }
  }
}

19、Received true for a non-boolean attribute xxx

当我们在 React 中开发自定义组件的时候,如果定义了一个布尔类型的 prop,在使用这个组件的时候,如果我们直接传递布尔值或者只传递单个属性,React 都会抛出这个异常。

// 定义一个Child组件
const Child = ({visible}:{visible:boolean}) => {
	return <div>{visible && <span>666</span>}</div>
}
export default Child

// 使用Child组件
<Child visible/> // Received `true` for a non-boolean attribute
<Child visible={true}/> // Received `true` for a non-boolean attribute

这是 React 本身的限制(因为不带引号的 HTML 属性可能会导致许多问题),你需要改变一下写法:

<Child visible={1}/>   // 用 0 和 1 代替布尔值
<Child visible="true"/>  // 直接传递字符串形式(注意组件内部判断)

// 还有如下写法也会抛出同样的异常
// ❌ This will throw the above error
<Component className={hidden && 'hidden'} />
// ✔️ Use a ternary instead
<Component className={hidden ? 'hidden' : undefined}  />

参考How to fix the ‘Received “true” for a non-boolean attribute’ error


🤠 希望能给遇到同样相关问题的你一点帮助,也为了记录。

14
广告 广告

评论区