【Recat组件,生命周期,路由,性能,setState】

一、React中,父子组件生命周期的执行顺序

React的生命周期从广义上分为三个阶段:挂载、渲染、卸载,因此可以把React的生命周期分为两类:挂载卸载过程和更新过程

挂载卸载过程

  1. constructor,完成了React数据的初始化;

  2. componentWillMount,组件初始化数据后,未渲染DOM前;

  3. componentDidMount,组件第一次渲染完成,dom节点已经生成;

  4. componentWillUnmount,组件的卸载和数据的销毁。

更新过程

  1. componentWillReceiveProps (nextProps),父组件改变后的props需要重新渲染组件时;

  2. shouldComponentUpdate(nextProps,nextState),主要用于性能优化(部分更新),因为react父组件的重新渲染会导致其所有子组件的重新渲染,这个时候不需要所有子组件都跟着重新渲染,然后return false就可以阻止组件的更新;

  3. componentWillUpdate (nextProps,nextState),shouldComponentUpdate返回true后,组件进入重新渲染的流程;

  4. componentDidUpdate(prevProps,prevState),组件更新完毕后触发;

  5. render(),渲染时触发。

父子组件加载顺序

父子组件的挂载生命周期函数,子组件的挂载钩子先被触发;卸载时,子组件的卸载钩子后被触发。

在挂载函数上注册监听器,可以与页面交互,也就是说所有挂载钩子都是在父组件实际挂载到dom树上触发,不过是在父组件挂载后依次触发子组件的 componentDidmount ,最后再触发自身的挂载钩,componentDidMount 其实就是是异步钩子。

相反,卸载的时候父节点先被移除,再从上至下依次触发子组件的卸载钩子;

在卸载钩子上卸载监听器,这说明 componentWillUnmount 在父组件从dom树上卸载前触发的,先触发自身的卸载钩子,但此时并未从dom树上剥离,然后依次尝试触发所有子组件的卸载钩子,最后,父组件从dom树上完成实际卸载。

二、对React Router的理解?常用的Router组件有哪些?

是什么

react-router前端路由,可以实现无刷新的条件下切换显示不同的页面

路由的本质就是页面的URL发生改变时,页面的显示结果可以根据URL的变化而变化,但是页面不会刷新

因此,可以通过前端路由可以实现单页面(SPA)应用

react-router主要分成了几个不同的包:

  • react-router: 实现了路由的核心功能

  • react-router-dom: 基于 react-router,加入了在浏览器运行环境下的一些功能

  • react-router-native:基于 react-router,加入了 react-native 运行环境下的一些功能

  • react-router-config: 用于配置静态路由的工具库

有哪些

react-router-dom的常用API,主要是提供了一些组件:

  • BrowserRouter、HashRouter

  • Route

  • Link、NavLink

  • switch

  • redirect

BrowserRouter、HashRouter

Router中包含了对路径改变的监听,并且会将相应的路径传递给子组件

BrowserRouterhistory模式,HashRouter模式

使用两者作为最顶层组件包裹其他组件

import { BrowserRouter as Router } from "react-router-dom";

export default function App() {
  return (
    <Router>
      <main>
        <nav>
          <ul>
            <li>
              < a href=" ">Home</ a>
            </li>
            <li>
              < a href="/about">About</ a>
            </li>
            <li>
              < a href="/contact">Contact</ a>
            </li>
          </ul>
        </nav>
      </main>
    </Router>
  );
}

Route

Route用于路径的匹配,然后进行组件的渲染,对应的属性如下:

  • path 属性:用于设置匹配到的路径

  • component 属性:设置匹配到路径后,渲染的组件

  • render 属性:设置匹配到路径后,渲染的内容

  • exact 属性:精准匹配,只有精准匹配到完全一致的路径,才会渲染对应的组件

import { BrowserRouter as Router, Route } from "react-router-dom";

export default function App() {
  return (
    <Router>
      <main>
        <nav>
          <ul>
            <li>
              < a href="/">Home</ a>
            </li>
            <li>
              < a href="/about">About</ a>
            </li>
            <li>
              < a href="/contact">Contact</ a>
            </li>
          </ul>
        </nav>
        <Route path="/" render={() => <h1>Welcome!</h1>} />
      </main>
    </Router>
  );
}

Link、NavLink

通常路径的跳转是使用Link组件,最终会被渲染成a元素,其中属性to代替a标题的href属性

Link 避免了不必要的重新渲染

Link 跳转做的三件事情:

  • 有onclick那就执行onclick

  • click的时候阻止a标签默认事件

  • 根据跳转 href,用 history 跳转,此时只是链接变了,并没有刷新页面

NavLink是在Link基础之上增加了一些样式属性,例如组件被选中时,发生样式变化,可以设置NavLink的属性:

  • activeStyle:活跃时(匹配时)的样式

  • activeClassName:活跃时添加的class

<NavLink to="/" exact activeStyle={{color: "red"}}>首页</NavLink>
<NavLink to="/about" activeStyle={{color: "red"}}>关于</NavLink>
<NavLink to="/profile" activeStyle={{color: "red"}}>我的</NavLink>

如果需要实现js实现页面的跳转,通过Route作为顶层组件包裹其他组件后,页面组件就可以接收到一些路由相关的东西,比如props.history

const Contact = ({ history }) => (
  <Fragment>
    <h1>Contact</h1>
    <button onClick={() => history.push("/")}>Go to home</button>
    <FakeText />
  </Fragment>
);
props `中接收到的`history`对象具有一些方便的方法,如`goBack`,`goForward`,`push

redirect

用于路由的重定向,当这个组件出现时,就会执行跳转到对应的to路径中

const About = ({
  match: {
    params: { name },
  },
}) => (
  // props.match.params.name
  <Fragment>
    {name !== "tom" ? <Redirect to="/" /> : null}
    <h1>About {name}</h1>
    <FakeText />
  </Fragment>
)

上述组件当接收到的路由参数name 不等于 tom 的时候,将会自动重定向到首页

switch

swich组件的作用适用于当匹配到第一个组件的时候,后面的组件就不应该继续匹配

<Switch>
  <Route exact path="/" component={Home} />
  <Route path="/about" component={About} />
  <Route path="/profile" component={Profile} />
  <Route path="/:userid" component={User} />
  <Route component={NoMatch} />
</Switch>

如果不使用switch组件进行包裹

除了一些路由相关的组件之外,react-router还提供一些hooks,如下:

  • useHistory

  • useLocation

useHistory

useHistory可以让组件内部直接访问history,无须通过props获取

import { useHistory } from "react-router-dom";

const Contact = () => {
  const history = useHistory();
  return (
    <Fragment>
      <h1>Contact</h1>
      <button onClick={() => history.push("/")}>Go to home</button>
    </Fragment>
  );
};

useLocation

useLocation 会返回当前 URLlocation对象

import { useLocation } from "react-router-dom";

const Contact = () => {
  const { pathname } = useLocation();

  return (
    <Fragment>
      <h1>Contact</h1>
      <p>Current URL: {pathname}</p >
    </Fragment>
  );
};

参数传递

这些路由传递参数主要分成了三种形式:

  • 动态路由的方式

  • search传递参数

  • to传入对象

动态路由

动态路由的概念指的是路由中的路径并不会固定

例如将pathRoute匹配时写成/detail/:id,那么 /detail/abc/detail/123都可以匹配到该Route

<NavLink to="/detail/abc123">详情</NavLink>

<Switch>
    ... 其他Route
    <Route path="/detail/:id" component={Detail}/>
    <Route component={NoMatch} />
</Switch>

获取参数方式如下:

1console.log(props.match.params.xxx)

search传递参数

在跳转的路径中添加了一些query参数;

<NavLink to="/detail2?name=why&age=18">详情2</NavLink>

<Switch>
  <Route path="/detail2" component={Detail2}/>
</Switch>

获取形式如下:

1console.log(props.location.search)

to传入对象

传递方式如下:

<NavLink to={{
    pathname: "/detail2", 
    query: {name: "kobe", age: 30},
    state: {height: 1.98, address: "洛杉矶"},
    search: "?apikey=123"
  }}>
  详情2
</NavLink>

获取参数的形式如下:

1console.log(props.location)

三、React中的VM不一定提高性能

因为 VM(视图模型) 只是通过 diff 算法避免了一些不需要变更的 DOM 操作,最终还是要操作 DOM 的,并且 diff 的过程也是有成本的。

对于某些场景,比如都是需要变更 DOM 的操作,因为 VM 还会有额外的 diff 算法的成本在里面,所以 VM 的方式并不会提高性能,甚至比原生 DOM 要慢。

框架的意义在于掩盖底层的 DOM 操作,用更声明式的方式来描述要做什么,从而让代码更容易维护。

没有任何框架可以比纯手动的优化 DOM 操作更快,因为框架的 DOM 操作层需要应对任何上层 API 可能产生的操作,它的实现必须是普适的。

四、在 React 中可以做哪些性能优化?

  • 使用 shouldComponentUpdate 避免不需要的渲染,需要根据业务进行些取舍;在有子组件的情况下,为了避免子组件的重复渲染,可以通过父组件来判断子组件是否需要 PureRender

  • 将 props 设置为数组或对象:每次调用 React 组件都会创建新组件,就算传入的数组或对象的值没有改变,他们的引用地址也会发生改变

  • 将函数的绑定移动到构造函数内:可以避免每次都绑定事件。

  • 使用 immutable 不可变数据,在我们项目中使用引用类型时,为了避免对原始数据的影响,一般建议使用 浅拷贝(shallowCopy) 和深拷贝(deepCopy) 对数据进行处理,但是这样会造成 CPU 和 内存的浪费,所以推荐使用 immutable

    • 降低了“可变”带来的复杂度

    • 节省内存,immutable 使用结构共享尽量复用内存,没有被引用的对象会被垃圾回收

    • 可以更好的做撤销/重做,复制/粘贴

    • 不会有并发问题(因为数据本身是不可变的)

  • 给子组件设置一个唯一的 key,因为在 diff 算法中,会用 key 作为唯一标识优化渲染

五、setState 之后发生了什么

简单版本

React 利用状态队列机制实现了 setState 的“异步”更新,避免频繁的重复更新 state。

首先将新的 state 合并到状态更新队列中,然后根据更新队列和 shouldComponentUpdate 的状态来判断是否需要更新组件

复杂版本

  • enqueueSetState 将 state 放入队列中,并调用 enqueueUpdate 处理要更新的 Component

  • 如果组件当前正处于 update 事务中,则先将 Component 存入 dirtyComponent 中。否则调用batchedUpdates 处理。

  • batchedUpdates 发起一次 transaction.perform() 事务

  • 开始执行事务初始化,运行,结束三个阶段

    • 初始化:事务初始化阶段没有注册方法,无方法要执行

    • 运行:执行 setSate 时传入的 callback 方法

    • 结束:更新 isBatchingUpdates 为 false,并执行 FLUSH_BATCHED_UPDATES 这个 wrapper 中的close方法,FLUSH_BATCHED_UPDATES在close阶段,会循环遍历所有的 dirtyComponents,调用updateComponent 刷新组件,并执行它的 pendingCallbacks, 也就是 setState 中设置的 callback。

flush_batched_updates

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

北海屿鹿

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

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

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

打赏作者

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

抵扣说明:

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

余额充值