基于redux状态的Youtube组件的条件呈现(cookie接受)



在我的Gatsby网站上,由于GDPR的原因,我想要一个Youtube组件,只有当用户接受cookie时才会显示。如果他们拒绝了cookie,我希望它能为重置他们的偏好提供指导,如果cookie接受状态未知(比如他们使用的扩展程序阻止了cookie通知),我希望说它不知道是否允许cookie,如果你没有看到横幅,请检查你的广告屏蔽程序。

我已经通过react redux设置了全局状态,它检查"acceptCookies"cookie和显示横幅的所有逻辑等(这都是使用react cookie同意完成的),并将state.acceptCookies设置为true。这在各种页面上都起到了作用,例如,如果接受cookie,我只想显示一个联系表格。

这是我的组件(我将其包含在任何要有Youtube视频的页面中,并将Youtube代码作为道具传递给它):

import React from "react"
import { useSelector } from "react-redux"
import { Alert } from "react-bootstrap"
import { Link } from "gatsby"
const YouTubeVideo = (props) => {
const acceptCookies = useSelector(state => state.acceptCookies)
return (
<>
{acceptCookies === '' &&
<Alert variant="warning">
<p>
We cannot show this YouTube video as cookies  have not been accepted,
but are required for YouTube to work.  To comply with GDPR law, the
video is not displayed until you have accepted cookies.
</p>
<p>
If you do not see the cookie banner, please try resetting your
preferences from the <Link to="/privacy-policy">Privacy Policy</Link> or
disabling any content blockers.
</p>
</Alert>
}
{acceptCookies === 'false' &&
<Alert variant="warning">
<p>
We cannot show this YouTube video as cookies have been declined, but
are required for YouTube to work.  To comply with GDPR law, the video
is not displayed.
</p>
<p>
You may reset your preferences from
the <Link to="/privacy-policy">Privacy Policy</Link>
</p>
</Alert>
}
{acceptCookies === 'true' &&
<div className="embed-container">
<iframe src={`https://www.youtube.com/embed/${props.code}?rel=0`}
title={`YouTube Video ${props.code}`}
frameBorder="0"
allowFullScreen
>
</iframe>
</div>
}
</>
)
}
export default YouTubeVideo

我尝试过其他语法,比如使用if语句和多个渲染块,没有区别的

我试着把它重写为一个类,并使用connect(),没有区别的

就我的一生而言,我无法让它正常工作。它在开发模式下运行良好-初始状态显示"cookie未被接受"警报,拒绝显示"cookie已被拒绝"警报,接受显示视频,一切都很好。我恢复了精神,一切正常。

但一旦我构建并部署了这个网站,它的行为就非常奇怪。一开始似乎很好——初始状态显示"cookie未被接受"警报,拒绝显示"cookie已被拒绝"警报,接受显示视频。然后刷新页面。。。。它在警报中显示了一个小版本的视频!(没有警报文本)。这真的很奇怪。这就像它遵循"cookie不被接受"的逻辑,开始绘制警报,然后在写段落之前,达到某种种族条件,并呈现代码中的"cookie被接受"部分,即视频。我一点也不明白,我希望它能呈现出这三件事中的一件,而不是以某种方式将结果拼凑在一起。

我在这里错过了什么?我完全困惑了。我想我一定是在预渲染中被什么绊倒了,但就是无法理解。

编辑:为了更清楚地了解情况,我想至少尝试将acceptCookies设置为静态"true",看看问题是我的渲染(在这种情况下,它仍然无法正确显示)还是全局状态实现(在那种情况下,会),并证明是后者。真正的问题是,我不知道如何在Gatsby中正确地执行cookie权限或全局状态(将其"缓存"为全局状态,以便在接受cookie后,输出将更新而不必刷新页面),并且只能真正地混合使用有关vanilla React和Gatsby的教程。我可能最好重写它——在这一点上,我不记得我用了哪本教程了!

不过,补充一下,这就是我的

gatsby-ssr.js和gatsby-browser.js都包含:

import wrapWithProvider from "./wrap-with-provider"
export const wrapRootElement = wrapWithProvider

包装与供应商js:

import React from "react"
import { Provider } from "react-redux"
import createStore from "./src/state/createStore"
// eslint-disable-next-line react/display-name,react/prop-types
export default ({ element }) => {
// Instantiating store in `wrapRootElement` handler ensures:
//  - there is fresh store for each SSR page
//  - it will be called only once in browser, when React mounts
const store = createStore()
return <Provider store={store}>{element}</Provider>
}

src/state/createStore.js:

import { createStore as reduxCreateStore } from "redux"
import Cookies from 'js-cookie'
const reducer = (state, action) => {
if (action.type === `ACCEPT_COOKIES`) {
return Object.assign({}, state, {
acceptCookies: 'true',
})
}
if (action.type === `DECLINE_COOKIES`) {
return Object.assign({}, state, {
acceptCookies: 'false',
})
}
if (action.type === `RESET_COOKIES`) {
return Object.assign({}, state, {
acceptCookies: '',
})
}
return state
}
const initialState = { acceptCookies: Cookies.get('acceptCookies') || '' }
const createStore = () => reduxCreateStore(reducer, initialState)
export default createStore

cookie同意通知是layout.js的一部分,因此无论何时需要,它都会出现在每一页上:

import CookieConsent from "react-cookie-consent"
const OurCookieConsent = ({ acceptCookies, accept, decline }) => (
<CookieConsent
location="bottom"
enableDeclineButton={true}
cookieName="acceptCookies"
onDecline={decline}
onAccept={accept}
>
<p>This website uses cookies for:</p>
<ul>
<li>REQUIRED: Saving your cookie preferences</li>
<li>OPTIONAL: The contact form on the "Contact Us" page - for spam
control (reCAPTCHA).</li>
<li>OPTIONAL: Embedded YouTube videos e.g. videos in our news pages
</li>
</ul>
<p>Declining consent will disable the features marked as OPTIONAL.</p>
<p>For more in-depth detail or to withdraw or re-establish your
consent at any time, please see the <Link to="/privacy-policy">Privacy Policy</Link>.</p>
</CookieConsent>
)
OurCookieConsent.propTypes = {
acceptCookies: PropTypes.string.isRequired,
accept: PropTypes.func.isRequired,
decline: PropTypes.func.isRequired,
}
const mapStateToProps = ({ acceptCookies }) => {
return { acceptCookies }
}
const mapDispatchToProps = dispatch => {
return {
accept: () => dispatch({ type: `ACCEPT_COOKIES` }),
decline: () => dispatch({ type: `DECLINE_COOKIES` })
}
}
const ConnectedCookieConsent = connect(mapStateToProps, mapDispatchToProps)(OurCookieConsent)

然后ConnectedCookieConsent包含在布局中。

我似乎已经让它工作了。这取决于Gatsby在生产网站上的工作效率-它通过Node.js预渲染了很多HTML,然后当托管网站加载和水合时,它只更新它认为需要的内容。它没有意识到整个预渲染的"外部"div(由于SSR期间没有cookie,因此呈现为"未知"状态)需要重新渲染,只更新可以肯定的是,有些东西已经改变了:内在的内容。

因此,该组件需要";知道";完全刷新自己,只有在状态或道具发生变化时才会这样做。最初的水合作用不算是一种变化(因为盖茨比的观点是,应用程序不应该关心它是在SSR中还是在浏览器中),所以它被留在了那种";"边缘地带";。您必须触发状态更改,这显然可以通过useEffect()钩子来实现,但我个人认为最简单的方法是将其转换为类并使用componentDidMount()。然后需要在呈现函数中使用该状态,以便它知道";如果状态发生变化,那么我需要重新呈现自己";。如果你想在cookie通知被接受后立即显示视频(不刷新),那么你还需要componentDidUpdate()(并且只需要在状态不同的情况下更新状态,否则会导致无限循环,因此下面代码中的If语句)。

这很复杂,我仍然不能100%理解,但这是我根据这个问题的答案对情况的粗略理解:

你能在不调用setState的情况下强制React组件重新发送吗?

这个:

https://github.com/gatsbyjs/gatsby/issues/12413

因此,我的工作组件是:

import React from "react"
import { connect } from "react-redux"
import { Alert } from "react-bootstrap"
import { Link } from "gatsby"
function mapStateToProps(state) {
return { acceptCookies: state.acceptCookies }
}
class YouTubeVideo extends React.Component {
state = {
acceptCookies: 'undecided',
}
componentDidMount() {
this.setState({
acceptCookies: this.props.acceptCookies,
})
}
componentDidUpdate() {
if (this.state.acceptCookies !== this.props.acceptCookies) {
this.setState({
acceptCookies: this.props.acceptCookies,
})
}
}
render () {
const { acceptCookies } = this.state
if (acceptCookies === 'undecided') {
return (
<Alert variant="warning">
<p>
We cannot show this YouTube video as cookies  have not been accepted,
but are required for YouTube to work.  To comply with GDPR law, the
video is not displayed until you have accepted cookies.
</p>
<p>
If you do not see the cookie banner, please try resetting your
preferences from the <Link to="/privacy-policy">Privacy Policy</Link> or
disabling any content blockers.
</p>
</Alert>
)
}
if (acceptCookies === 'false') {
return(
<Alert variant="warning">
<p>
We cannot show this YouTube video as cookies have been declined, but
are required for YouTube to work.  To comply with GDPR law, the video
is not displayed.
</p>
<p>
You may reset your preferences from
the <Link to="/privacy-policy">Privacy Policy</Link>
</p>
</Alert>
)
}
if (acceptCookies === 'true') {
return (
<div className="embed-container">
<iframe src={`https://www.youtube.com/embed/${this.props.code}?rel=0`}
title={`YouTube Video ${this.props.code}`}
frameBorder="0"
allowFullScreen
>
</iframe>
</div>
)
}
return (
<p><a href={`https://www.youtube.com/watch?v=${this.props.code}`}>YouTube Video</a></p>
)
}
}

export default connect(mapStateToProps)(YouTubeVideo)

综上所述。。。。

该组件有自己的本地状态,acceptCookies最初设置为"未决定",这是SSR将生成的内容(以及谷歌或禁用JS的人最终会看到的内容),如果你有cookie,它也是你在加载几毫秒后看到的内容,直到JS工作人员加载并使页面水合。这确实会导致短暂的不需要的内容或FOUC,但我认为在遵守GDPR的情况下,人们对此无能为力。它需要加载javascript来检查cookie,并知道它有权呈现YouTube嵌入(以及谷歌向用户转储的大量未经请求的cookie,因此需要整个练习)。一旦它水合,就会调用componentDidMount(),并将本地状态更新为当前acceptCookies道具(稍后我们将讨论)。如果不同(即您已经接受了cookie),则组件会看到状态更改并完全重新呈现自己。

在底部,您可以看到组件是通过connect(mapStateToProps)导出的,connect从redux中获取全局状态(上下文),并将其传递给mapStateToProps函数,该函数抛出acceptCookies道具,然后将其绑定回组件。幸运的是,这种情况发生在componentDidMount()之前,因此它知道如何正确更新状态。

当acceptCookies cookie更新时(通过我使用的单独cookie通知机制),我们已经知道createStore.js中的"ACCEPT_COKIES"reducer被触发,导致reduce状态更新为acceptCookies='true'。connect函数随后更新组件的props以匹配(类似于以前的工作方式),但由于我们现在在render函数中使用的是state而不是props,这不足以触发重新渲染-我们还需要componentDidUpdate()来获取props的更改并更新状态以匹配。

如果我做了什么傻事,请纠正我,但这似乎已经奏效了。我希望这种冗长的言辞能帮助其他处于我这种处境的人。

我喜欢我提出的只提供链接的后备方案(因为根据GDPR,只要是外部链接,你就不对其他网站放置的cookie负责,只要你将他们的网站嵌入为iframe,或者它被视为你的"一部分"),最终可能会更新我的错误/未决定的输出来做到这一点(并提供一个小的解释),而不是发出一个大的当面警报,拒绝用户观看视频的机会。

我会这样写YoutubeVideo组件:

import React from "react"
import { useSelector } from "react-redux"
import { Alert } from "react-bootstrap"
import { Link } from "gatsby"
const YouTubeVideo = (props) => {
const acceptCookies = useSelector((state) => state.acceptCookies)
if (acceptCookies === "")
return (
<Alert variant="warning">
<p>
We cannot show this YouTube video as cookies have not been
accepted, but are required for YouTube to work. To comply
with GDPR law, the video is not displayed until you have
accepted cookies.
</p>
<p>
If you do not see the cookie banner, please try resetting
your preferences from the{" "}
<Link to="/privacy-policy">Privacy Policy</Link> or
disabling any content blockers.
</p>
</Alert>
)
if (acceptCookies === "false")
return (
<div className="embed-container">
<iframe
src={`https://www.youtube.com/embed/${props.code}?rel=0`}
title={`YouTube Video ${props.code}`}
frameBorder="0"
allowFullScreen
></iframe>
</div>
)
if (acceptCookies === "true")
return (
<div className="embed-container">
<iframe
src={`https://www.youtube.com/embed/${props.code}?rel=0`}
title={`YouTube Video ${props.code}`}
frameBorder="0"
allowFullScreen
></iframe>
</div>
)
// Maybe add a default ?
return (
<Alert variant="warning">
<p>
Not sure what's up here, but hey, better safe than sorry :)
</p>
</Alert>
)
}

最新更新