【JavaScript教程】【Svelte从入门到精通】入门篇——组件的生命周期

零 JavaScript教程评论61字数 5825阅读19分25秒阅读模式

每个组件从创建开始,到销毁结束这个过程,都有一个生命周期。在不同的生命周期阶段,组件对外提供一个方法,使调用组件的开发人员能够更灵活地在每个生命周期阶段对组件进行控制,这类方法便是生命钩子函数。

Svelte的生命周期钩子有:文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/15116.html

  • onMount
  • beforeUpdate
  • afterUpdate
  • onDestroy
  • tick

调用时机

如果使用服务端渲染(SSR),除了onDestroy,其余生命周期函数不会在SSR运行期间执行。文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/15116.html

在Svelte中,生命周期函数只能在组件初始化时编写,以便将回调绑定到组件的实例,切勿将生命周期函数放在一些如setTimeout、setInterval之类的异步方法中。文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/15116.html

虽然我们需要确保生命周期函数在组件初始化时被调用,但至于从何处调用它们则无关紧要。比如,我们可以把生命周期放到一个方法中:文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/15116.html

javascript文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/15116.html

复制代码
// app.js
import { onMount,onDestroy } from 'svelte';

export const startInterval = () => {
  let timer = null;
  let count = 0;

  onMount(() => {
    console.log('onMount');
    timer = setInterval(() => {
      count++;
      console.log('count');
    }, 1000)
  });

  onDestroy(() => {
    console.log('onDestroy');
    if (timer) {
      clearInterval(timer);
    }
  })
}

html文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/15116.html

复制代码
<script>
  import { startInterval } from './app.js';

  startInterval();
</script>

<div>App</div>

这种情况下,生命周期依然能够确保在组件初始化时被调用。文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/15116.html

onMount

在组件挂载到DOM后立即执行的回调。文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/15116.html

typescript文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/15116.html

复制代码
onMount(callback: () => void)

onMount(callback: () => () => void)

onMount接收一个回调函数作为参数,如果这个回调函数内返回一个函数,则在卸载组件时调用该函数。文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/15116.html

我们可以看下源码,在Svelte项目的packages/svelte/src/runtime/internal/Component.js文件内:

javascript

复制代码
/** @returns {void} */
export function mount_component(component, target, anchor) {
  const { fragment, after_update } = component.$$;
  fragment && fragment.m(target, anchor);
  // onMount happens before the initial afterUpdate
  add_render_callback(() => {
    const new_on_destroy = component.$$.on_mount.map(run).filter(is_function);
    // if the component was destroyed immediately
    // it will update the `$$.on_destroy` reference to `null`.
    // the destructured on_destroy may still reference to the old array
    if (component.$$.on_destroy) {
      component.$$.on_destroy.push(...new_on_destroy);
    } else {
      // Edge case - component was destroyed immediately,
      // most likely as a result of a binding initialising
      run_all(new_on_destroy);
    }
    component.$$.on_mount = [];
  });
  after_update.forEach(add_render_callback);
}

我们可以看到,会执行onMount的方法后拿到一个值,接着会判断组件内有没有声明onDestroy生命周期钩子,有则添加到onDestroy数组中,在执行onDestroy后执行这个返回值,没有则直接执行这个返回值。在React的useEffecthook中,有相同的功能。

javascript

复制代码
useEffect(() => {
  return () => {}
}, []);

我们演示一个Svelte的onMount返回值的例子:

html

复制代码
<script>
  // Child.svelre
  import { onMount } from 'svelte';

  onMount(() => {
    console.log('child mount');
    return () => {
      console.log('child destroy 1');
    }
  })
</script>

<div>child</div>

html

复制代码
<script>
  // Father.svelte
  import Child from './Child.svelte';
  let count = 0;

  const updateCount = () => {
    count++;
  }
</script>


<button on:click={updateCount}>add</button>
{#if count == 0}
<Child />
{/if}

这个例子是,我们在父级页面有一个变量count,通过count变化来控制子组价的显隐。 执行代码,控制台会依次输出:child mount -> child destroy 1。

然后我们添加onDestroy钩子:

html

复制代码
<script>
  import { onMount, onDestroy } from 'svelte';

  onMount(() => {
    console.log('child mount');
    return () => {
      console.log('child destroy 1');
    }
  })

  onDestroy(() => {
    console.log('child destroy 2');
  })
</script>

<div>child</div>

执行代码,控制台会依次打印出:child mount -> child destroy 2 -> child destroy 1。

对于兄弟组件,onMount会根据组件调用的顺序,从上往下执行。而对于父子组件,当子组件的onMount执行完毕之后,才会执行父组件的onMount,也就是从内到外。

html

复制代码
<script>
  import { onMount } from "svelte";
  import Child from "./Child.svelte";
  import Child2 from "./Child2.svelte";

  onMount(() => {
    console.log("fahter mount");
  });
</script>

<Child />
<Child2 />

我们定义三个组件:一个父组件,两个子组件。在三个组件内分别调用onMount。 执行后,我们能看到:child mount 1 -> child mount 2 -> father mount。

beforeUpdate

在DOM更新之前执行,首次回调运行在onMount初始化之前。

typescript

复制代码
beforeUpdate(callback: () => void)

演示一个例子:

html

复制代码
<script>
  import { beforeUpdate } from "svelte";

  let count = 0;
  let str = "";

  const updateData = () => {
    count++;
    setTimeout(() => {
      str += "s";
    }, 1000);
  };

  beforeUpdate(() => {
    console.log("before update", count, str);
  });
</script>

<button on:click={updateData}>update</button>
<span>{count}</span><span>{str}</span>

加载页面后,我们会首先看到控制台打印出before update 0。点击按钮更新,看到打印出before update 1,1s后看到打印出before update 1 s。

beforeUpdate

需要注意的是,如果组件中同时存在beforeUpdateonMount,首次beforeUpdate回调会在onMount之前执行。如果我们在上述代码中调用onMount钩子,然后执行相同步骤,则看到:

onMount

afterUpdate

在组件渲染之后执行的回调。

typescript

复制代码
afterUpdate(callback: () => void)

对于兄弟组件,beforeUpdateafterUpdate依然是按照组件调用的顺序,从上往下执行。而对于父子组件,会先执行父组件的beforeUpdate,然后执行子组件的beforeUpdate

当子组件的beforeUpdate执行完毕之后,再执行父组件的afterUpdate,最后执行子组件的afterUpdate

html

复制代码
<script>
  import { onMount, onDestroy, beforeUpdate, afterUpdate } from "svelte";
  import Child from "./Child.svelte";
  import Child2 from "./Child2.svelte";
  let count = 0;

  const updateCount = () => {
    count++;
  };

  beforeUpdate(() => {
    console.log("fater beforeupdate");
  });

  onMount(() => {
    console.log("father mount");
  });

  afterUpdate(() => {
    console.log("father afterupdate");
  });

  onDestroy(() => {
    console.log("father destroy");
  });
</script>

<button on:click={updateCount}>add</button>
{#if count <= 1}
  <Child {count} />
  <Child2 {count} />
{/if}

子组件内容如下,Child1.svelte和Child2.svelte只有输出内容的序号不同,其他无异:

html

复制代码
<script>
  // Child.svelte
  import { onMount, onDestroy, beforeUpdate, afterUpdate } from 'svelte';

  export let count;

  beforeUpdate(() => {
    console.log('child beforeupdate 1')
  });

  onMount(() => {
    console.log('child mount 1');
  });

  afterUpdate(() => {
    console.log('child afterupdate 1');
  });

  onDestroy(() => {
    console.log('child destroy 1');
  });
</script>

<div>child</div>
{count}

在初次执行时:

afterUpdate

点击按钮更新后:

afterUpdate

onDestroy

在组件卸载后运行的回调。

javascript

复制代码
onDestroy(callback: () => void)

兄弟组件之间的onDestroy依然是根据调用顺序从上往下。而父子组件则是从外到内,先执行父组件的onDestroy,再执行子组件的onDestroyonMount返回的函数会在组件销毁的时候执行,这个函数会在onDestroy之后执行,我们在之前了解onMount时已讲解过源码。

tick

tick函数与其他生命周期钩子不同,我们可以随时调用它,而不用等待组件首次初始化。它返回一个Promise,在任何一个state状态发生变化时立即resolve。

typescript

复制代码
promise: Promise = tick()

在Svelte中,当改变一个state状态时,不会立即更新DOM,而是会等到下一个微任务时更新,在等待期间会持续监听是否有其他state状态改变,然后在这个微任务中统一更新DOM,这样可以减少一些无用功,让浏览器更有效地批量处理这些事情。

我们在开发过程中可能会遇到这种问题:组件中的某个状态更新了,但DOM没有更新,而我恰恰就想获取dom的值,这时tick函数便能派上用场了!(不错,可以看成是Vue中的$nextTick())

html

复制代码
<script>
  let count = 0;

  const addCount = () => {
    count++;
    let countDom = document.querySelector('#count');
    if (countDom) {
      console.log('count text', countDom.innerHTML);
    }
  }
</script>

<button on:click={addCount}>add</button>

<span id="count">{count}</span>

tick

我们可以看到,即使页面上已经更新了,操作DOM获取的值依然是旧的数据。

为了解决上述问题,我们可以:

html

复制代码
<script>
  import { tick } from 'svelte';
  let count = 0;

  const addCount = async () => {
    count++;
    await tick();
    let countDom = document.querySelector('#count');
    if (countDom) {
      console.log('count text', countDom.innerHTML);
    }
  }
</script>

<button on:click={addCount}>add</button>

<span id="count">{count}</span>

tick

而当我们使用了tick之后,能够取到最新的值。

小结

本章我们学习了:

  • Svelte生命周期函数的作用
  • 各生命周期函数在父子组件渲染时的执行顺序
  • tick的作用

零
  • 转载请务必保留本文链接:https://www.0s52.com/bcjc/javascriptjc/15116.html
    本社区资源仅供用于学习和交流,请勿用于商业用途
    未经允许不得进行转载/复制/分享

发表评论