05 React

147 阅读12分钟

hooks 使用的时候有一些限制条件,为什么

React hooks 有一些使用上的限制条件,主要是为了确保组件的行为一致性和性能优化:

  1. 只能在函数组件或自定义 hook 中调用:不能在普通的 JavaScript 函数中调用 hooks,这是为了确保 hooks 的调用顺序在每次渲染时保持一致。

  2. 不能在循环、条件或嵌套函数中调用:hooks 必须在组件的顶层调用,这样 React 才能在多次渲染之间保持 hooks 调用的顺序一致。

  3. useEffect 的依赖数组:在使用 useEffect 时,必须正确设置依赖数组,以避免不必要的重新渲染或遗漏依赖项导致的错误。

这些限制条件是为了确保 React 能够正确地管理组件的状态和副作用,从而提高应用的性能和稳定性。

对 useCallback、useMemo 这两个 hook 的理解,有什么样的区别,适合在什么场景下使用

useCallbackuseMemo 都是 React 提供的 hooks,用于性能优化,但它们的用途和工作方式有所不同:

  1. useCallback:
  • 用途:返回一个 memoized 回调函数。
  • 使用场景:当你希望在组件重新渲染时,避免创建不必要的新函数实例,特别是在将回调函数传递给子组件时。
  • 示例
    const memoizedCallback = useCallback(() => {
      doSomething(a, b)
    }, [a, b])
    
  1. useMemo:
  • 用途:返回一个 memoized 值。
  • 使用场景:当你希望在组件重新渲染时,避免执行不必要的计算,特别是当计算过程开销较大时。
  • 示例
    const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b])
    

区别

  • useCallback 是用来 memoize 函数的,而 useMemo 是用来 memoize 值的。
  • useCallback(fn, deps) 相当于 useMemo(() => fn, deps)

适用场景

  • 使用 useCallback 优化回调函数的传递,避免子组件不必要的重新渲染。
  • 使用 useMemo 优化计算过程,避免每次渲染都进行重复计算。

通过合理使用这两个 hooks,可以有效提升 React 应用的性能。

是不是所有的变量或者函数都需要用这两个 hook 进行包裹,能不能量化一下,什么情况下需要使用

并不是所有的变量或者函数都需要用 useCallbackuseMemo 进行包裹。它们主要用于性能优化,具体使用场景如下:

  1. useCallback
  • 使用场景:当你有一个回调函数需要传递给子组件,并且这个子组件依赖于该回调函数时,使用 useCallback 可以避免子组件不必要的重新渲染。
  • 量化标准:如果回调函数在组件的每次渲染中都保持不变,并且传递给了依赖于它的子组件,则应使用 useCallback
  1. useMemo
  • 使用场景:当你有一个计算开销较大的值,并且这个值在组件的每次渲染中都保持不变时,使用 useMemo 可以避免不必要的重复计算。
  • 量化标准:如果计算过程非常耗时,并且计算结果在依赖项不变的情况下也保持不变,则应使用 useMemo

总结

  • 使用 useCallback 优化回调函数的传递,避免子组件不必要的重新渲染。
  • 使用 useMemo 优化计算过程,避免每次渲染都进行重复计算。

通过合理使用这两个 hooks,可以有效提升 React 应用的性能,但不应滥用,只有在明确需要优化的情况下才使用。

包裹后性能一定会好吗,为什么?

React 组件中绑定一个事件跟直接操作 DOM 绑定一个事件有什么差别

在 React 组件中绑定事件和直接操作 DOM 绑定事件有以下几个差别:

  1. 事件绑定方式
  • React 组件:使用 JSX 语法绑定事件,例如 onClickonChange 等。React 会自动处理事件绑定和解绑。

    function MyComponent() {
      const handleClick = () => {
        console.log('Button clicked')
      }
    
      return <button onClick={handleClick}>Click me</button>
    }
    
  • 直接操作 DOM:使用原生 JavaScript 或 jQuery 直接操作 DOM 元素并绑定事件。

    document.getElementById('myButton').addEventListener('click', function () {
      console.log('Button clicked')
    })
    
  1. 事件委托
  • React 组件:React 使用事件委托机制,将所有事件绑定到根节点上,然后通过事件冒泡处理具体的事件。这种方式提高了性能,减少了内存消耗。
  • 直接操作 DOM:需要手动处理事件委托,可能会导致性能问题,特别是在大量元素需要绑定事件时。
  1. 事件解绑
  • React 组件:React 会在组件卸载时自动解绑事件,避免内存泄漏。
  • 直接操作 DOM:需要手动解绑事件,否则可能会导致内存泄漏。
  1. 事件处理上下文
  • React 组件:事件处理函数的上下文是组件实例,可以直接访问组件的状态和属性。
  • 直接操作 DOM:事件处理函数的上下文是绑定事件的 DOM 元素,需要手动管理状态和属性。
  1. 跨平台兼容性
  • React 组件:React 处理了不同浏览器之间的事件兼容性问题,开发者无需关心具体的实现细节。
  • 直接操作 DOM:需要手动处理不同浏览器之间的兼容性问题。

总之,在 React 组件中绑定事件更加简洁、安全和高效,而直接操作 DOM 绑定事件则需要更多的手动管理和兼容性处理。

对类组件和函数组件的理解,它们的区别,什么情况下写类组件更好,什么情况下写函数组件更好

React 提供了两种定义组件的方式:类组件和函数组件。

  1. 类组件
  • 定义方式:通过 ES6 的 class 语法定义,继承自 React.Component

  • 状态管理:使用 this.state 来管理组件的状态。

  • 生命周期方法:可以使用生命周期方法(如 componentDidMountcomponentDidUpdatecomponentWillUnmount 等)来处理组件的生命周期。

  • 示例

    class MyComponent extends React.Component {
      constructor(props) {
        super(props)
        this.state = { count: 0 }
      }
    
      componentDidMount() {
        // 组件挂载后执行
      }
    
      render() {
        return (
          <div>
            <p>{this.state.count}</p>
            <button
              onClick={() => this.setState({ count: this.state.count + 1 })}>
              Increment
            </button>
          </div>
        )
      }
    }
    
  1. 函数组件
  • 定义方式:通过 JavaScript 函数定义。

  • 状态管理:使用 React hooks(如 useStateuseEffect 等)来管理状态和副作用。

  • 生命周期方法:通过 hooks(如 useEffect)来处理类似生命周期的方法。

  • 示例

    function MyComponent() {
      const [count, setCount] = useState(0)
    
      useEffect(() => {
        // 组件挂载后执行
      }, [])
    
      return (
        <div>
          <p>{count}</p>
          <button onClick={() => setCount(count + 1)}>Increment</button>
        </div>
      )
    }
    

区别

  • 语法和结构:类组件使用 class 语法,函数组件使用函数语法。
  • 状态和生命周期:类组件通过 this.state 和生命周期方法管理状态和生命周期,函数组件通过 hooks 管理。
  • 性能:函数组件通常更简洁,性能更好,因为它们不需要实例化类。

适用场景

  • 类组件:如果需要使用生命周期方法,或在现有代码中已经使用了类组件,可以继续使用类组件。
  • 函数组件:对于新项目或需要简洁代码的场景,推荐使用函数组件。函数组件结合 hooks 可以实现类组件的大部分功能,并且代码更简洁易读。

总之,函数组件是现代 React 开发的推荐方式,但在某些特定情况下,类组件仍然有其使用价值。

函数组件不能实现的类组件特性

虽然函数组件结合 hooks 可以实现大部分类组件的功能,但仍有一些特性是函数组件无法直接实现的:

  1. Error Boundaries(错误边界)
  • 类组件可以通过 componentDidCatchstatic getDerivedStateFromError 方法实现错误边界,捕获子组件的渲染错误。
  • 函数组件目前无法直接实现错误边界功能,需要通过类组件来实现。
  1. 实例方法
  • 类组件可以定义实例方法,通过 this 访问组件实例。
  • 函数组件没有实例,因此无法定义实例方法。
  1. Ref 访问实例
  • 类组件可以通过 ref 获取组件实例,访问实例方法和属性。
  • 函数组件通过 ref 获取的是 DOM 元素或子组件的 ref,而不是组件实例。

尽管如此,React 团队正在不断改进函数组件的功能,未来可能会引入更多特性来弥补这些差距。

如果 react 组件的 props 是一个复杂对象会怎样

如果 React 组件的 props 是一个复杂对象,可能会导致以下问题:

  1. 性能问题
  • 复杂对象在传递给子组件时,如果没有使用 useMemouseCallback 进行优化,可能会导致子组件不必要的重新渲染。
  • 复杂对象的深层比较开销较大,React 默认使用浅比较,可能无法检测到对象内部的变化。
  1. 不可变性
  • 复杂对象的属性如果被直接修改,可能会导致不可预期的行为。应确保 props 是不可变的,使用 Object.freeze 或深拷贝来保护对象。
  1. 调试困难
  • 复杂对象在调试时不易追踪和理解,特别是在对象嵌套层级较深时。

解决方案

  1. 使用 useMemo 优化
  • 使用 useMemo 来缓存复杂对象,避免不必要的重新渲染。
const memoizedObject = useMemo(() => complexObject, [complexObject])
  1. 保持不可变性
  • 确保传递给组件的对象是不可变的,避免直接修改对象属性。
const newObject = { ...complexObject, newProperty: value }
  1. 简化对象结构
  • 尽量简化对象结构,减少嵌套层级,提升可读性和维护性。

通过合理管理复杂对象的 props,可以提高 React 组件的性能和可维护性。

路由传参方式

在 React 中,路由传参有几种常见的方式:

  1. URL 参数
  • 通过 URL 中的参数进行传递,通常使用 React Router 的 useParams 钩子来获取参数。

  • 示例:

    // 定义路由
    ;<Route path="/user/:id" component={User} />
    
    // 获取参数
    function User() {
      let { id } = useParams()
      return <div>User ID: {id}</div>
    }
    
  1. 查询字符串
  • 通过 URL 的查询字符串进行传递,使用 useLocation 钩子获取查询字符串并解析参数。

  • 示例:

    // 定义路由
    ;<Route path="/search" component={Search} />
    
    // 获取查询字符串参数
    function Search() {
      let location = useLocation()
      let query = new URLSearchParams(location.search)
      let term = query.get('term')
      return <div>Search Term: {term}</div>
    }
    
  1. 状态对象
  • 通过路由的状态对象进行传递,使用 useLocation 钩子获取状态对象。

  • 示例:

    // 传递状态对象
    history.push('/details', { id: 123 })
    
    // 获取状态对象
    function Details() {
      let location = useLocation()
      let { id } = location.state || {}
      return <div>Details ID: {id}</div>
    }
    
  1. 上下文(Context)
  • 使用 React 的 Context API 在组件树中传递参数。

  • 示例:

    const UserContext = React.createContext()
    
    function App() {
      return (
        <UserContext.Provider value={{ id: 123 }}>
          <User />
        </UserContext.Provider>
      )
    }
    
    function User() {
      const { id } = useContext(UserContext)
      return <div>User ID: {id}</div>
    }
    

通过这些方式,可以在 React 应用中灵活地传递路由参数,满足不同的需求。

next.js 原理

Next.js 是一个基于 React 的服务端渲染(SSR)框架,它提供了一些关键特性,使得开发者能够轻松构建高性能的 Web 应用。以下是 Next.js 的一些核心原理:

  1. 服务端渲染(SSR)
  • Next.js 支持服务端渲染,即在服务器端生成 HTML 内容,然后发送到客户端。这有助于提高页面加载速度和 SEO 性能。

  • 示例:

    export async function getServerSideProps(context) {
      const res = await fetch(`https://api.example.com/data`)
      const data = await res.json()
      return { props: { data } }
    }
    
    function Page({ data }) {
      return <div>{data}</div>
    }
    
    export default Page
    
  1. 静态站点生成(SSG)
  • Next.js 支持静态站点生成,即在构建时生成静态 HTML 文件。这对于内容不频繁变化的页面非常有用。

  • 示例:

    export async function getStaticProps() {
      const res = await fetch(`https://api.example.com/data`)
      const data = await res.json()
      return { props: { data } }
    }
    
    function Page({ data }) {
      return <div>{data}</div>
    }
    
    export default Page
    
  1. 客户端渲染(CSR)
  • Next.js 也支持客户端渲染,即在客户端获取数据并渲染页面。可以结合 React 的 useEffect 钩子来实现。

  • 示例:

    function Page() {
      const [data, setData] = useState(null)
    
      useEffect(() => {
        fetch(`https://api.example.com/data`)
          .then((res) => res.json())
          .then((data) => setData(data))
      }, [])
    
      return <div>{data}</div>
    }
    
    export default Page
    
  1. 路由系统
  • Next.js 提供了基于文件系统的路由机制,开发者只需在 pages 目录下创建文件,即可自动生成对应的路由。
  • 示例:
    pages/
    ├── index.js    // 对应路径:/
    ├── about.js    // 对应路径:/about
    └── blog/
      └── [id].js // 对应路径:/blog/:id
    
  1. API 路由
  • Next.js 允许在 pages/api 目录下创建 API 路由,方便开发者构建后端 API。
  • 示例:
    // pages/api/data.js
    export default function handler(req, res) {
      res.status(200).json({ message: 'Hello, world!' })
    }
    

通过这些特性,Next.js 提供了灵活的渲染方式和强大的路由系统,使得开发者能够轻松构建高性能的 Web 应用。

CSR 和 SSR 打包区别

在 React 应用中,客户端渲染(CSR)和服务端渲染(SSR)有不同的打包方式和流程:

  1. 客户端渲染(CSR)
  • 打包方式:使用工具(如 Webpack)将所有的 JavaScript、CSS 和其他资源打包成静态文件,通常是一个单页应用(SPA)。
  • 渲染流程:初始加载时,服务器发送一个基本的 HTML 文件和打包后的 JavaScript 文件。浏览器下载并执行 JavaScript 文件,生成和渲染页面内容。
  • 优点:开发体验好,前后端分离,适合交互复杂的应用。
  • 缺点:首次加载时间较长,对 SEO 不友好。
  1. 服务端渲染(SSR)
  • 打包方式:除了打包客户端代码外,还需要打包服务器端代码。Next.js 等框架会处理这个过程,将 React 组件预渲染成 HTML。
  • 渲染流程:初始请求时,服务器生成完整的 HTML 页面并发送给客户端。浏览器加载页面后,再下载和执行 JavaScript 文件,使页面变得可交互。
  • 优点:首次加载速度快,对 SEO 友好。
  • 缺点:服务器负载较高,开发复杂度增加。

通过了解 CSR 和 SSR 的打包区别,可以根据项目需求选择合适的渲染方式,优化应用性能和用户体验。csr 和 ssr 打包区别