理解 Svelte 中的上下文(从 React 上下文转换)
Posted
技术标签:
【中文标题】理解 Svelte 中的上下文(从 React 上下文转换)【英文标题】:Understanding Context in Svelte (convert from React Context) 【发布时间】:2021-08-12 06:18:50 【问题描述】:我有一个使用 ContextAPI 来管理身份验证的反应应用程序,我正在尝试在 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>
)
此上下文在其他Login.js
组件中使用,如下所示:
import Auth from "./Authenticate"
const Login = () =>
const currentUser, login = Auth()
在App.js
我有:
import AuthComp from "./Authenticate";
function App()
return (
<AuthComp>
<div> All others go here </div>
</AuthComp>
);
如何在 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>
【问题讨论】:
【参考方案1】:从 React 上下文迁移到 Svelte
Svelte 和 React 中的上下文可能看起来很相似,但实际上它们的使用方式不同。因为从本质上讲,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>
请注意,上下文涉及两件事,一个键和一个值。上下文设置为特定键,然后可以使用该键检索值。与 React 不同,您不需要导出函数来检索上下文。上下文的键和值都可以是任何东西。如果可以将其保存到变量中,则可以将其设置为上下文。您甚至可以将对象用作键!
商店
如果您的数据需要在应用中的多个位置保持同步,那么商店就是您的最佳选择。商店是反应式的,这意味着它们可以在创建后更新。与 React 或 Svelte 中的上下文不同,存储不会简单地将数据传递给它们的子级。应用的任何部分都可以创建商店,应用的任何部分都可以读取商店。您甚至可以在单独的 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
然后在一个组件里面,你就可以订阅商店了。
<script>
import writableStore from './mystore'
</script>
$writableStore
美元符号订阅商店。现在,无论何时更新商店,组件都会自动重新渲染。
使用带有上下文的商店
现在我们有了商店和上下文,我们可以创建“反应式上下文”(我刚刚提出的一个术语,但它有效)。存储很棒,因为它们是反应式的,上下文很适合将数据传递给子组件。但我们实际上可以通过上下文传递一个商店。这使得上下文具有反应性并且存储范围是有限的。
<!-- 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
现在,每当父项中的存储更新时,子项也会更新。当然,Store 可以做的远不止这些,但如果你想复制 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 />
道具
在大多数情况下,道具在 React 和 Svelte 中的功能完全相同。存在一些差异,因为 Svelte 道具可以利用双向绑定(不是必需的,但可能)。不过,这确实是一个不同的对话,本教程非常擅长使用道具进行双向绑定。
Svelte 中的身份验证
好的,现在,让我们看看如何创建身份验证包装组件。
创建授权商店 通过上下文传递身份验证存储 使用 Firebase 的onAuthStateChanged
监听身份验证状态的变化
在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
【讨论】:
真正的好答案! 除非你在做服务器端渲染,否则我认为没有明显的区别。在onMount
之外调用它会更快地运行它,因为onMount
会等到组件出现在屏幕上。对于服务器端渲染,我一直只是检查typeof window !== 'undefined
(因为onAuthStateChanged
需要在浏览器中运行)而不是大多数时间等待onMount
。不确定这是否是最好的方法,但它对我有用。
保持商店范围的目的是什么?如果您可以将商店同时导入父级和子级,则不需要上下文。这有优势还是反模式?顺便说一句,这种比较对我帮助很大。
你说得对,使用商店不需要上下文。这样做并不是一种反模式。以这种方式使用存储时要注意的主要事项是,您需要在单独的文件中对其进行初始化——您不能从组件内部导出存储(据我所知)。如果您想确定商店的范围,或者您不想处理关联上下文,那么使用商店的上下文非常有用。一个例子是 和 组件。 ListItems 应该始终在列表中,并且可以有多个列表。在这种情况下,确定上下文的范围是惯用的。这有帮助吗?
非常有帮助的解释,谢谢@Nick!事实上,有一种方法可以从 <script context="module">
内部的组件中导出商店,这里是 REPL【参考方案2】:
在 Svelte 中,在父组件中使用 setContext(key, value)
设置上下文,子组件可以使用 getContext(key)
访问 value
对象。请参阅the docs 了解更多信息。
在您的情况下,上下文将像这样使用:
<script>
import getContext, setContext from 'svelte';
import auth from '../firebase';
import writable from 'svelte/store';
// you can initialize this to something else if you want
let currentUser = writable(null)
let loading = true
// maybe you're looking for `onMount` or `onDestroy`?
const unmount = auth.onAuthStateChanged(user =>
currentUser.set(user)
loading = false
);
function login(email, password)
return auth.signInWithEmailandPassWord(email,password)
function logout()
return auth.signOut()
const value = currentUser, login, signUp
setContext('Auth', value)
</script>
#if !loading
<slot></slot>
/if
在这里,currentUser
、login
和 signup
(不确定它来自哪里?)设置为带有 setContext()
的上下文。要使用这个上下文,你可能会有这样的事情:
<!-- App -->
<AuthComp>
<!-- Some content here -->
<Component />
</AuthComp>
<!-- Component.svelte -->
<script>
import getContext from 'svelte'
const currentUser, login, signup = getContext('Auth')
// you can subscribe to currentUser with $currentUser
</script>
<div>some content</div>
正如文档中所写,上下文不是反应式,因此currentUser
首先转换为store,因此它可以在子级中为subscribed to。至于useEffect
,Svelte 有lifecycle functions,您可以使用它在不同的点运行代码,例如onMount
或onDestroy
。
如果您是 Svelte 新手,他们的 tutorial 非常全面,有很多示例可供您参考。
希望这有帮助!
【讨论】:
这太棒了。关于您的问题 -maybe you're looking for onMount or onDestroy?
- 这个想法是复制 useEffect
以在身份验证状态更改时更改 currentUser
和 loading
值。我想如果afterUpdate
我需要什么,但我不知道这是否会给我与useEffect
相同的东西。我不确定卸载功能是否能让我明白这一点。
@Kay 在不了解auth.onAuthStateChanged
函数的作用的情况下,我认为我无法为您提供帮助(也许这是一个不同的问题,因为这个问题主要与上下文有关)。有可能你根本不需要任何生命周期钩子,可以直接调用它。
onAuthStateChanged
为用户登录状态的变化添加一个观察者,并在用户登录或退出时触发。
我猜你正在使用firebase auth。我也用过它,我所做的只是在onMount
的App.svelte
中订阅onAuthStateChanged
。然后,基于user
参数,我设置了一些反应值,用于确定要渲染的内容。以上是关于理解 Svelte 中的上下文(从 React 上下文转换)的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Svelte App 内渲染 React App 或在 React App 内渲染 Svelte App?
有没有一种方便的方法来引用 Svelte 组件中的 DOM 元素?