前端React面试基础系列(React基础篇)

React 基础系列

1. 什么是受控组件和非受控组件?

受控组件

像表单元素在用户输入时,像<input> <select>等元素需要绑定一个 change 事件,当组件的状态发生变化时就会触发 change 事件,更新组件的 state,这种就是受控组件。

非受控组件

如果一个表单组件没有 value、props 时,就可以称为非受控组件。
非受控组件可以使用 ref 来从 DOM 获取到表单的值。

2. 类组件和函数组件有什么区别?

  1. 类组件中存在生命周期函数,而函数组件中我们使用的是 hooks 来替代生命周期函数。
  2. 多次 render 时,类组件实际上只会 new 一次,函数组件会被调用多次。
  3. 心智模型上:函数式组件捕获了渲染所用的值;而类组件在读取 this 的时候,虽然 props 是不可变的,但是 this 是永远可变的,因此能在渲染方法或者生命周期方法中得到最新的实例。具体可参考:https://juejin.cn/post/6844904049146331150?searchId=20240417204216AA0C5BA8B04E8337D027

3. state 和 props 的区别

state

state 时用来存储组件中的状态值,必须要使用 setState 来修改,state 值的变化会造成组件的重新渲染。

props

props 时一个外部传入到组件内的参数,具有可读性和不可变性,只能通过外部组件来传入新的 Props 来重新渲染子组件。

区别

  1. props 是不可修改的,state 是在组件中被创建,可以通过 setState 来进行修改的。
  2. props 是传递给组件的,state 则是组件自身内部管理的。

为什么 props 是只读的?
因为 React 的设计中,是根据单向数据流的设计模式,使状态可预测。如果子组件可以修改 props 的值,那么就会造成状态的不可预测性,给后续的维护及调试带来很大的困扰。

4. 父子组件的通信方式

  1. props 和 callback 方式
    • 父组件 -> 通过自身 state 改变,重新渲染,传递 props -> 通知子组件
    • 子组件 -> 通过调用父组件 props 方法 -> 通知父组件。
  2. ref 方式
    • 类组件可以通过 ref 直接获取组件实例,实现组件通信
    • 函数组件 forwardRef + useImperativeHandle 实现组件通信
  3. React-redux 或 React-mobx 状态管理方式
    • 状态管理库
  4. context 上下文方式
    • 将一些数据存储在全局数据中
  5. event bus 事件总线(类似于 PubSub 库)
    • react 中并不推荐,因为需要手动绑定和解绑
    • 一定程度上违背了 React 数据流向原则
    • 小项目还好,大项目难以维护

5. 类组件的setState和函数组件的useState异同

相同点

都更新视图,底部调用了scheduleUpdateFiber方法,在事件驱动情况下都有批量更新规则。

不同点
  1. setState只要调用了就会执行更新;useState会浅比较2次state是否相同
  2. setState有专门监听变化的回调函数;useState只能通过useEffect
  3. setState底层处理上主要是和老的state合并;useState则重新赋值

6. 为什么React组件中 return 一个对象而不是一个元素时会报错

因为对象不具备迭代接口,必须要时原型或者自己身上有[Symbol.iterator]属性才可以,而数组是有迭代接口的,所以可以直接迭代。

7. 什么是 JSX

JSX 是一种表达式也是 React 的一种标准书写方式,允许我们使用书写 html 的方式去写 react 元素。

最终会变成什么

  1. jsx 元素节点首先会被 babel 编译成React.createElement形式
  2. createElement 处理之后,会被转换成 react element 对象。
    image.png
  3. 在调和阶段,React element 对象的每一个子节点都会形成一个与之对应的 fiber 对象,然后通过 siblingreturnchild 将每一个 fiber 对象联系起来。
babel工作原理

babel的一种工作原理(html 文件中 script 导入时):在 script 标签中,如果 type 不是text/javascript或者module,游览器不会去解析 javascript 内部的代码,babel 会监听全局的document.contentLoad(意味着当前页面所有的 script 标签全部生成完毕)。

babel 直接拿到所有的 script 标签,并读 script 上的属性getAttributes("type"),如果是text/babel,会把里面的代码全部拿过来,然后转换,生成新的 script 标签插入到页面。

8. 说说有哪些hooks

1. useState

用来声明状态变量,接受的参数是一个初始值;返回一个数组,数组的第[0]项是当前的状态值,[1]项是用来改变状态值的方法函数。

2. useEffect

副作用,react首次渲染和之后的每次渲染都可以调用一遍传给useEffect的函数,可以模拟类组件的3个生命周期:componentDidMountcomponentDidUpdatecomponentWillUnmount

当第二个参数传入一个空数组时,相当于只在首次渲染的时候执行;如果传入一个变量,就会进行一次浅比较,变量发生变化时,会执行传入的函数。

3. useRef
  • 可以获取当前元素的所有属性,并且返回一个可变的ref对象。
  • 可以用来缓存数据

不是仅为真实 dom 服务,构建一个状态出来,但是这个状态时直接脱离 react 控制的,他的变化也不会造成重新渲染,同时状态还不会因为组件的重新渲染而被初始化

如果不使用 useRef 去处理真实 dom 会出现什么问题?
  • 使用 state 会造成没必要的重新渲染,用全局变量又会造成闭包问题
4. useMemo(优化)

场景: 当一个父组件中调用了一个子组件的时候,父组件的 state 发生变化,会导致父组件更新,而子组件虽然没有发生改变,但也会进行更新。

  • useMemo可以减少不必要的渲染和循环;
  • 减少子组件的渲染次数;
  • 同时通过特定的依赖进行更新,避免很多不必要的开销。
5. useCallback(优化)

useMemo类似,useMemo返回的是函数运行的结果useCallback返回的是函数。这个函数是父组件传递子组件的一个函数,防止做无关的刷新,这个组件必须配合memo来使用。

6. useContext

就是类组件中的context,可以用来存储一些全局的状态。

7. useReducer

是一个用于状态管理的hook api,与reducer函数相关。她接受2个参数,分别为reducer函数和初始状态。返回一个数组,第[0]项是state值,第[1]项是dispatch函数。

8. useLayoutEffect

useEffect 几乎完全一致,唯一的区别是 useLayoutEffect 会在所有的 dom 变更之后同步调用(意味着会完全阻塞后续工作),而 useEffect 是在所有的 dom 变更之后异步调用。

9. useTransition(react18,优化)

const [isPending, startTransition] = useTransition()是一个帮助我们在不阻塞UI的情况下更新状态。

返回1个数组,[0]项告诉我们是否存在待处理的transition;[1]项是一个startTransition函数,用这个方法可以把状态更新标记为transition

function TabContainer() {  
    const [isPending, startTransition] = useTransition();  
    const [tab, setTab] = useState('about');  
    function selectTab(nextTab) {  
        startTransition(() => {  
            setTab(nextTab);  
        });  
    }  
    // ……
}

更多建议查看React官网https://react.docschina.org/reference/react/hooks

9.fowardRef

是一个高阶组件,接收一个组件作为参数返回一个新的组件。给函数组件扩展了一个 ref 属性
给子组件挂 ref 是要要求子组件去追加一个 forwardRef 的,同时 forwardRef 会将得到的这个 ref 属性通过第二个参数传递给真实的函数组件。

10.useImperativeHandle

第一个参数是 ref,意味着在底层会去改这个 ref 的 current 属性
第二个参数是一个函数,这个函数的返回值最终会被丢到这个 ref.current 属性上去
第三个参数是依赖项,意味着依赖项不变的话 ref 的 current 值不会被重新赋值

11. React18有哪些更新

  1. setState自动批处理

    • 17中只有react合成事件会进行批处理,legacy模式;
    • 18中所有事件都进行批处理,提高了性能,concurrent模式。
  2. 支持并发模式的渲染

  3. 去掉了对IE浏览器的支持

  4. flushSync,用于退出批量更新

  5. react组件返回值更新

    • 17中返回空组件只能返回null,返回undefined会报错
    • 18中支持null和undefined
  6. 支持useId(在服务器和客户端生成相同的唯一一个id,避免hydrating的不兼容)

  7. 新增hooksuseInsertionEffect:只建议在css in js库中使用,在dom生成之前执行,在useLayoutEffect之前,一般用于提前注入脚本

  8. useSyncExternalStore:解决外部数据撕裂问题

  9. Suspense不再需要fallback捕获

  10. Concurrent Mode:并发模式,可以帮助应用保持响应,根据用户的设备性能和网速进行调整,通过渲染可中断来修复阻塞渲染机制。该模式下,可以同时更新多个状态;

  11. strict mode更新:使用严格模式时,React会对每个组件返回两次渲染,以便你观察一些意想不到的结果,在react17中去掉了一次渲染的控制台日志,以便让日志容易阅读。react18取消了这个限制,第二次渲染会以浅灰色出现在控制台日志

12. Redux工作原理

image.png

场景
  • 跨层级组件数据共享与通信
  • 一些需要持久化的全局数据
工作原理

单例模式

主要构成
Store

全局状态管理对象

Reducer

一个纯函数,根据旧state和props更新state

Action

改变状态的唯一方式是调用dispatch函数,传递一个action给这个函数

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
在前两中,我们介绍了 React Router 和路由的基础知识,以及如何在应用中使用路由。在本中,我们将深入学习 React Router,并介绍一些更高级的用法。 ## 动态路由 在前面的教程中,我们已经学习了如何定义静态路由。但是在实际开发中,我们通常需要处理动态路由。例如,我们可能需要在 URL 中传递参数,以便根据参数来渲染不同的组件。 在 React Router 中,我们可以使用 `:param` 来定义动态路由参数。例如,我们可以定义一个动态路由 `/user/:id`,其中 `:id` 表示一个动态参数,表示用户的 ID。当浏览器访问 `/user/123` 时,React Router 会自动将 `123` 作为参数传递给对应的组件。 下面是一个简单的例子: ```jsx import { BrowserRouter as Router, Route, Link } from "react-router-dom"; function User({ match }) { return <h1>Hello, {match.params.id}!</h1>; } function App() { return ( <Router> <div> <ul> <li> <Link to="/user/123">User 123</Link> </li> <li> <Link to="/user/456">User 456</Link> </li> </ul> <Route path="/user/:id" component={User} /> </div> </Router> ); } ``` 在上面的例子中,我们定义了一个动态路由 `/user/:id`,其中 `:id` 表示用户的 ID。当浏览器访问 `/user/123` 时,React Router 会自动将 `123` 作为参数传递给 `User` 组件,并显示 `Hello, 123!`。 同样,当浏览器访问 `/user/456` 时,React Router 会自动将 `456` 作为参数传递给 `User` 组件,并显示 `Hello, 456!`。 ## 嵌套路由 在实际开发中,我们经常需要在一个页面中嵌套多个组件。在 React Router 中,我们可以使用嵌套路由来实现这个功能。 具体来说,我们可以在一个组件中定义多个 `<Route>` 组件,从而实现嵌套路由。例如,我们可以定义一个嵌套路由 `/user/:id/posts`,其中 `:id` 表示用户的 ID,`posts` 表示用户的帖子列表。 下面是一个简单的例子: ```jsx import { BrowserRouter as Router, Route, Link } from "react-router-dom"; function User({ match }) { return ( <div> <h1>Hello, {match.params.id}!</h1> <ul> <li> <Link to={`${match.url}/posts`}>Posts</Link> </li> </ul> <Route path={`${match.path}/posts`} component={Posts} /> </div> ); } function Posts() { return <h2>Posts</h2>; } function App() { return ( <Router> <div> <ul> <li> <Link to="/user/123">User 123</Link> </li> <li> <Link to="/user/456">User 456</Link> </li> </ul> <Route path="/user/:id" component={User} /> </div> </Router> ); } ``` 在上面的例子中,我们定义了一个嵌套路由 `/user/:id/posts`,其中 `:id` 表示用户的 ID,`posts` 表示用户的帖子列表。当浏览器访问 `/user/123/posts` 时,React Router 会自动将 `123` 作为参数传递给 `User` 组件,并显示 `Posts` 组件。 ## 路由守卫 在实际开发中,我们通常需要实现一些路由守卫功能,例如登录验证、权限控制等。在 React Router 中,我们可以使用 `Route` 组件的 `render` 属性来实现路由守卫。 具体来说,我们可以定义一个高阶组件 `AuthRoute`,用来检查用户是否登录。如果用户已登录,则渲染对应的组件;否则,跳转到登录页面。 下面是一个简单的例子: ```jsx import { BrowserRouter as Router, Route, Link, Redirect } from "react-router-dom"; function AuthRoute({ component: Component, ...rest }) { const isAuthenticated = localStorage.getItem("isAuthenticated"); return ( <Route {...rest} render={(props) => isAuthenticated ? ( <Component {...props} /> ) : ( <Redirect to={{ pathname: "/login", state: { from: props.location } }} /> ) } /> ); } function Home() { return <h1>Welcome Home!</h1>; } function Login() { const login = () => { localStorage.setItem("isAuthenticated", true); }; return ( <div> <h2>Login</h2> <button onClick={login}>Login</button> </div> ); } function App() { return ( <Router> <div> <ul> <li> <Link to="/">Home</Link> </li> <li> <Link to="/login">Login</Link> </li> </ul> <AuthRoute exact path="/" component={Home} /> <Route path="/login" component={Login} /> </div> </Router> ); } ``` 在上面的例子中,我们定义了一个高阶组件 `AuthRoute`,用来检查用户是否登录。如果用户已登录,则渲染对应的组件;否则,跳转到登录页面。具体来说,我们首先从本地存储中检查用户是否已登录,然后根据检查结果来渲染对应的组件或跳转到登录页面。 ## 总结 在本教程中,我们深入学习了 React Router,并介绍了一些更高级的用法。具体来说,我们学习了如何定义动态路由、嵌套路由和路由守卫。 希望通过本教程,你已经掌握了 React Router 的高级用法,并能够在实际开发中灵活使用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陈梵阿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值