理解 Svelte 中的 Context(从 React Context 转换)

Kay*_*Kay 2 reactjs svelte svelte-store svelte-3 context-api

我有一个使用 ContextAPI 来管理身份验证的 React 应用程序,我正在尝试在 Svelte 中实现类似的功能。[Web 开发简化][1]

Authenticate.js我有这个:

import React, { useContext, useState, useEffect } from "react"
import { auth } from "../firebase"

const AuthCt = React.createContext()

export function Auth() {
  return useContext(AuthCt)
}

export function AuthComp({ children }) {
  const [currentUser, setCurrentUser] = useState()
  const [loading, setLoading] = useState(true)

  function login(email, password) {
    return auth.signInWithEmailAndPassword(email, password)
  }

  function logout() {
    return auth.signOut()
  }

  useEffect(() => {
    const unmount = auth.onAuthStateChanged(user => {
      setCurrentUser(user)
      setLoading(false)
    })

    return unmount
  }, [])

  const value = {
    currentUser,
    login,
    signup
  }

  return (
    <AuthCt.Provider value={value}>
      {!loading && children}
    </AuthCt.Provider>
  )
}
Run Code Online (Sandbox Code Playgroud)

此上下文在其他Login.js组件中使用,如下所示:

import { Auth } from "./Authenticate"

const Login = () => {
  const { currentUser, login } = Auth()
Run Code Online (Sandbox Code Playgroud)

而在App.js我有:

import { AuthComp } from "./Authenticate";

function App() {
  return (
          <AuthComp>
               <div> All others go here </div>
          </AuthComp>
  );
}
Run Code Online (Sandbox Code Playgroud)

我如何在 Svelte 中实现这一点,尤其是Authenticate上下文?

我无法在 Svelte 中做太多事情,因为我不知道如何从这里开始。到目前为止我有 AuthComp.svelte。我不知道我是否在做正确的事情。

<script>
    import { getContext, setContext } from 'svelte';
    import  { auth } from '../firebase';
    import { writable } from 'svelte/store';

    let Auth = getContext('AuthCt')
    setContext('Auth', Auth)

    let currentUser;
    let loading = true;

    
     const unmount = auth.onAuthStateChanged(user => {
        currentUser = user;
        loading = false
     });


    function login(email, password) {
        return auth.signInWithEmailandPassWord(email,password)
    }
    
    function logout() {
       return auth.signOut()
    }
    
    const value = { currentUser, login, signUp }
    
</script>

<slot value={value}></slot>
Run Code Online (Sandbox Code Playgroud)

Nic*_*ick 6

从 React Context 迁移到 Svelte

Svelte 和 React 中的 Context 可能看起来很相似,但实际上它们的用法不同。因为就核心而言,Svelte 的上下文要受限得多。不过没关系。事实上,它实际上会使您的代码更易于编写和理解。

在 Svelte 中,您可以使用更多工具来在您的应用程序中传递数据(并保持同步),而不仅仅是上下文。每个人几乎都做一件事(让一切都可以预测),而且他们做得很好。其中,你有:

  • 语境
  • 商店
  • 道具

作为最近从 React 切换到 Svelte 的人,我想我可以帮助解释这些之间的一些差异,并帮助您避免我的一些概念错误。我还将讨论生命周期方法中的一些差异,因为如果您曾经使用useEffect,您可能会感到很迷茫,因为 Svelte 没有等效的 API。然而,在 Svelte 中将所有内容组合在一起将使一切变得简单。

语境

Svelte 中的上下文做一件事:将数据从父组件传递给任何子组件(不一定是直接子组件)。与 React 不同,上下文不是响应式的。组件挂载时设置一次,以后不再更新。我们将在一秒钟内进入“反应性上下文”。

<!-- parent.svelte -->

<script>
  import { setContext } from 'svelte'

  setContext('myContext', true)
</script>

<!-- child.svelte -->

<script>
  import { getContext } from 'svelte'

  const myContext = getContext('myContext')
</script>
Run Code Online (Sandbox Code Playgroud)

请注意,上下文涉及两件事,一个键和一个值。上下文设置为特定键,然后可以使用该键检索该值。与 React 不同,您不需要导出函数来检索上下文。上下文的键和值都可以是任何东西。如果可以将其保存到变量,则可以将其设置为上下文。您甚至可以使用对象作为键!

商店

如果您的数据需要在应用程序的多个位置保持同步,那么商店是您的最佳选择。商店是反应性的,这意味着它们可以在创建后更新。与 React 或 Svelte 中的上下文不同,store 不会简单地将数据传递给它们的孩子。应用程序的任何部分都可以创建商店,应用程序的任何部分都可以读取商店。您甚至可以在单独的 JavaScript 文件中在 Svelte 组件之外创建商店。

// mystore.ts
import { writable } from 'svelte/store'

// 0 is the initial value
const writableStore = writable(0)

// set the new value to 1
const writableStore.set(1)

// use `update` to set a new value based on the previous value
const writableStore.update((oldValue) => oldValue + 1)

export { writableStore }
Run Code Online (Sandbox Code Playgroud)

然后在组件内部,您可以订阅商店。

<script>
  import { writableStore } from './mystore'

</script>

{$writableStore}
Run Code Online (Sandbox Code Playgroud)

美元符号订阅商店。现在,每当商店更新时,组件都会自动重新渲染。

使用带有上下文的商店

现在我们有了存储和上下文,我们可以创建“反应性上下文”(我刚刚编造的一个术语,但它有效)。存储很棒,因为它们是反应式的,上下文非常适合将数据传递给子组件。但是我们实际上可以通过上下文向下传递商店。这使上下文具有反应性并且存储范围。

<!-- parent.svelte -->

<script>
  import { setContext } from 'svelte'
  import { writable } from 'svelte/store'

  const writableStore = writable(0)
  setContext('myContext', writableStore)
</script>

<!-- child.svelte -->

<script>
  import { getContext } from 'svelte'

  const myContext = getContext('myContext')
</script>

{$myContext}
Run Code Online (Sandbox Code Playgroud)

现在,每当存储在父级中更新时,子级也会更新。Stores 当然可以做的远不止这些,但如果你想复制 React 上下文,这是你在 Svelte 中可以得到的最接近的。它也少了很多样板!

使用“reactive context”和“useEffect”

Svelte 没有相当于useEffect. 相反,Svelte 具有响应式语句。文档/教程中有很多关于这些的内容,所以我会保持简短。

// doubled will always be twice of single. If single updates, doubled will run again.
$: doubled = single * 2

// equivalent to this

let single = 0
const [doubled, setDoubled] = useState(single * 2)

useEffect(() => {
  setDoubled(single * 2)
}, [single])
Run Code Online (Sandbox Code Playgroud)

Svelte 足够聪明,可以找出依赖关系,并且只根据需要运行每个反应式语句。如果你创建了一个依赖循环,编译器就会对你大吼大叫。

这意味着您可以使用反应式语句来更新存储(从而更新上下文)。在这里,valueStore输入的每次击键都会更新。由于此存储通过上下文向下传递,因此任何子项都可以获取输入的当前值。

<script>
  import { setContext } from 'svelte'
  import { writable } from 'svelte/store'

  // this value is bound to the input's value. When the user types, this variable will always update
  let value

  const valueStore = writable(value)

  setContext('inputContext', valueStore)

  $: valueStore.set(value)

</script>

<input type='text' bind:value />
Run Code Online (Sandbox Code Playgroud)

道具

大多数情况下,props 在 React 和 Svelte 中的功能完全相同。有一些不同之处,因为 Svelte 道具可以利用双向绑定(不是必需的,但可能)。不过,这确实是一次不同的对话,本教程非常擅长使用 props 教授双向绑定。

Svelte 中的身份验证

好的,现在完成所有这些之后,让我们看看如何创建身份验证包装器组件。

  • 创建一个授权商店
  • 通过上下文传递身份验证存储
  • 使用 FirebaseonAuthStateChanged监听身份验证状态的变化
  • 订阅 child 中的 auth store
  • onAuthStateChanged在父级销毁时取消订阅以防止内存泄漏
<!-- parent.svelte -->
<script>
  import { writable } from 'svelte/store'
  import { onDestroy, setContext } from 'svelte'

  import { auth } from '../firebase'

  const userStore = writable(null)

  const firebaseUnsubscribe = auth.onAuthStateChanged((user) => {
    userStore.set(user)
  })

  const login = (email, password) => auth.signInWithEmailandPassWord(email,password)

  const logout = () => auth.sinnOut()

  setContext('authContext', { user: userStore, login, logout })

  onDestroy(() => firebaseUnsubscribe())

</script>

<slot />

<!-- child.svelte -->
<script>
  import { getContext } from 'svelte'

  const { login, logout, user } = getContext('authContext')
</script>

{$user?.displayName}
Run Code Online (Sandbox Code Playgroud)

  • 真的很棒的答案! (12认同)
  • 你是对的,使用商店不需要上下文。这样做并不是一种反模式。以这种方式使用存储时要注意的主要事情是,您需要在单独的文件中对其进行初始化 - 您无法从内部组件导出存储(据我所知)。如果您想要确定商店的范围,或者您不想处理连接上下文,那么将上下文与商店结合使用非常有用。一个示例是 &lt;List&gt; 和 &lt;ListItem&gt; 组件。ListItems 应始终位于列表内,并且可以有多个列表。在这种情况下,惯用的做法是确定上下文范围。这有帮助吗? (3认同)
  • 天呐!你真的做到了! (3认同)
  • 除非您正在进行服务器端渲染,否则我认为没有明显的差异。在“onMount”之外调用它会更快地运行它,因为“onMount”会等待组件出现在屏幕上。对于服务器端渲染,我只是检查是否 `typeof window !== 'undefined` (因为 `onAuthStateChanged` 需要在浏览器中运行),而不是大多数时间等待 `onMount`。不确定这是否是最好的方法,但它对我有用。 (2认同)
  • 保持商店范围的目的是什么?如果您可以将商店导入到父级和子级中,则不需要上下文。这有优点还是反模式?顺便说一句,这种比较对我帮助很大。 (2认同)