从 React Context 迁移到 Svelte
Svelte 和 React 中的 Context 看起来很相似,但实际上它们的用法不同。因为从本质上讲,Svelte 的上下文要有限得多。但没关系。事实上,它实际上将使您的代码更易于编写和理解。
在 Svelte 中,您可以使用更多工具来在应用程序中传递数据(并保持同步),而不仅仅是上下文。每个人几乎只做一件事(让一切都可预测),而且做得很好。其中,您有:
作为最近从 React 切换到 Svelte 的人,我想我可以帮助解释它们之间的一些差异,并帮助您避免我的一些概念错误。我还将讨论生命周期方法的一些差异,因为如果您曾经使用useEffect
,您可能会感到非常失落,因为 Svelte 没有等效的 API。然而,在 Svelte 中将所有内容组合在一起将使一切变得简单。
Context
Svelte 中的 Context 做了一件事:将数据从父组件传递到任何子组件(不一定是直接子组件)。与 React 不同的是,上下文不是反应性的。组件挂载时设置一次,之后不会再次更新。我们稍后将讨论“反应式上下文”。
<!-- parent.svelte -->
<script>
import { setContext } from 'svelte'
setContext('myContext', true)
</script>
<!-- child.svelte -->
<script>
import { getContext } from 'svelte'
const myContext = getContext('myContext')
</script>
请注意,上下文涉及两件事:键和值。上下文设置为特定键,然后可以使用该键检索值。与 React 不同,您不需要导出函数来检索上下文。上下文的键和值都可以是任何内容。如果可以将其保存到变量中,则可以将其设置为上下文。您甚至可以使用一个对象作为键!
Stores
如果您的数据需要在应用程序的多个位置保持同步,那么商店就是您的最佳选择。商店是反应性的,这意味着它们可以在创建后进行更新。与 React 或 Svelte 中的上下文不同,存储并不简单地将数据传递给它们的子级。应用程序的任何部分都可以创建商店,并且应用程序的任何部分都可以读取商店。您甚至可以在 Svelte 组件之外的单独 JavaScript 文件中创建商店。
// mystore.ts
import { writable } from 'svelte/store'
// 0 is the initial value
const writableStore = writable(0)
// set the new value to 1
writableStore.set(1)
// use `update` to set a new value based on the previous value
writableStore.update((oldValue) => oldValue + 1)
export { writableStore }
然后在组件内,您可以订阅商店。
<script>
import { writableStore } from './mystore'
</script>
{$writableStore}
美元符号订阅商店。现在,每当商店更新时,组件都会自动重新渲染。
使用具有上下文的商店
现在我们有了商店和上下文,我们可以创建“反应性上下文”(我刚刚发明的一个术语,但它有效)。存储很棒,因为它们是响应式的,并且上下文非常适合将数据传递给子组件。但我们实际上可以通过 context 向下传递一个 store。这使得上下文成为反应式的并且存储范围被限定。
<!-- 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}
现在,每当父级中的商店更新时,子级也会更新。商店当然可以做更多的事情,但如果您想要复制 React 上下文,这是您在 Svelte 中可以得到的最接近的。它的样板也少了很多!
将“反应式上下文”与“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])
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 />
Props
大多数情况下,props 在 React 和 Svelte 中的功能完全相同。存在一些差异,因为 Svelte 道具可以利用双向绑定(不是必需的,但可能)。但这确实是一个不同的对话,并且该教程非常擅长教授道具的双向绑定。
Svelte 中的身份验证
好的,现在,在完成所有这些之后,让我们看看如何创建身份验证包装器组件。
- 创建一个授权存储
- 通过上下文传递身份验证存储
- 使用 Firebase 的
onAuthStateChanged
监听身份验证状态的变化
- 订阅 child 中的 auth 商店
- 取消订阅
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.signOut()
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}