视频 ref 只在刷新后渲染 + 功能组件不能给 refs
Posted
技术标签:
【中文标题】视频 ref 只在刷新后渲染 + 功能组件不能给 refs【英文标题】:Video ref only renders after refresh + function components cannot be given refs 【发布时间】:2021-10-14 23:58:02 【问题描述】:我有一个使用以下VideoPlayer
的 NEXT JS 应用程序
组件:
import React, useCallback, useEffect, useState, useContext from "react";
import useSocket, useSocketUpdate from "../Store";
import useRouter from "next/router";
import styles from "../styles/CallPage.module.css";
const VideoPlayer = (props: any) =>
const
isAdmin,
myVideo,
userVideo
: any = useSocket();
const router = useRouter();
useEffect(() =>
if (router.isReady && myVideo.current && userVideo.current)
myVideo?.current.load();
userVideo?.current.load();
, []);
return (
<div className=styles.grid_container>
stream && (
<div>
<video
className=styles.video
playsInline
muted
ref=myVideo
autoPlay
/>
<div className=styles.overlay>
<h2 className=styles.overlay_h2>OVERLAY</h2>
</div>
</div>
)
callAccepted && !callEnded ? (
<div>
<video
className=styles.video
playsInline
ref=userVideo
autoPlay
/>
<div className=styles.overlay>
<h2 className=styles.overlay_h2>OVERLAY2</h2>
</div>
</div>
) : (
isGuest &&
!callAccepted && (
<div className=styles.grid_container>
<div className=styles.no_match_content>
<h2 className=styles.h2_no_match>
Please wait until the host of this room let you in.
</h2>
<div className=styles.btn_no_match></div>
</div>
</div>
)
)
</div>
);
;
export default VideoPlayer;
来自以下Store
声明的 useSocket 挂钩正在检索 myVideo 引用:
import React,
createContext,
useState,
useRef,
useEffect,
useContext
from "react";
import io from "socket.io-client";
import Peer from "simple-peer";
import useRouter from "next/router";
const SocketContext = createContext();
const SocketUpdateContext = createContext();
const socket = io(`$process.env.NEXT_PUBLIC_SERVER_URL`);
function useSocket()
const useSocket = useContext(SocketContext);
if (useSocket === undefined)
throw new Error("useSocket must be used within a SocketProvider");
return useSocket;
function useSocketUpdate()
const useSocketUpdate = useContext(SocketUpdateContext);
if (useSocketUpdate === undefined)
throw new Error("useSocketUpdate must be used within a SocketProvider");
return useSocketUpdate;
const ContextProvider = ( children : any) =>
const router = useRouter();
const [callAccepted, setCallAccepted] = useState(false);
const [callEnded, setCallEnded] = useState(false);
const [stream, setStream] = useState<MediaStream>();
const [name, setName] = useState("");
const [call, setCall] = useState<any>();
const [me, setMe] = useState("");
const [isAdmin, setAdmin] = useState(false);
const [isGuest, setGuest] = useState(false);
const [idToCall, setIdToCall] = useState("");
const myVideo = useRef<htmlVideoElement>(null!);
const userVideo = useRef<HTMLVideoElement>(null!);
const connectionRef = useRef<Peer.Instance>(null!);
useEffect(() =>
navigator.mediaDevices
.getUserMedia( video: true, audio: true )
.then((currentStream) =>
setStream(currentStream);
if (myVideo.current)
myVideo.current.srcObject = currentStream;
);
socket.on("me", (id) =>
setMe(id);
);
socket.on("callUser", ( from, name: callerName, signal ) =>
setCall( isReceivingCall: true, from, name: callerName, signal );
);
, []);
const answerCall = () =>
setCallAccepted(true);
const peer = new Peer( initiator: false, trickle: false, stream );
peer.on("signal", (data) =>
socket.emit("answerCall", signal: data, to: call.from );
);
peer.on("stream", (currentStream) =>
if (userVideo.current) userVideo.current.srcObject = currentStream;
);
peer.signal(call.signal);
connectionRef.current = peer;
;
const callUser = (id: String) =>
// setAdmin(false);
const peer = new Peer( initiator: true, trickle: false, stream );
peer.on("signal", async (data) =>
socket.emit("callUser",
userToCall: id,
signalData: data,
from: me,
name
);
);
peer.on("stream", (currentStream) =>
if (userVideo.current) userVideo.current.srcObject = currentStream;
);
socket.on("callAccepted", (signal) =>
setCallAccepted(true);
peer.signal(signal);
);
if (connectionRef.current) connectionRef.current = peer;
;
const leaveCall = () =>
setCallEnded(true);
setAdmin(false);
if (connectionRef.current) connectionRef.current.destroy();
// if (typeof window !== "undefined") window.location.reload();
router.push("/home");
;
return (
<SocketContext.Provider
value=
call,
callAccepted,
myVideo,
userVideo,
stream,
name,
callEnded,
me,
isAdmin,
router,
isGuest,
user,
isAuthenticated,
isUnauthenticated,
isAuthenticating,
authError,
idToCall
>
<SocketUpdateContext.Provider
value=
callUser,
leaveCall,
answerCall,
setName,
setAdmin,
setGuest,
logout,
authenticate,
signup,
login,
setIdToCall
>
children
</SocketUpdateContext.Provider>
</SocketContext.Provider>
);
;
export ContextProvider, SocketContext, useSocket, useSocketUpdate ;
最后我在 CallPage
中渲染 VideoPlayer 组件:
import useEffect, useReducer, useState, useContext from "react";
import styles from "../styles/CallPage.module.css";
import Peer from "simple-peer";
import VideoPlayer from "../components/VideoPlayer";
import Page from "../components/UI/Page";
import Link from "next/link";
import Head from "next/head";
import useSocket, useSocketUpdate from "../Store";
const initialState = [];
const CallPage = () =>
const isAdmin, myVideo, isAuthenticated, isUnauthenticated, user : any =
useSocket();
if (isUnauthenticated)
return (
<Page>
<div className=styles.no_match_content>
<h2 className=styles.h2_no_match>
Please login first to access a call.
</h2>
<div className=styles.btn_no_match>
<Link passHref href="/login">
Login
</Link>
</div>
</div>
</Page>
);
let alertTimeout = null;
const [messageList, messageListReducer] = useReducer(
MessageListReducer,
initialState
);
useEffect(() =>
if (isAdmin)
setMeetInfoPopup(true);
, []);
return (
<div className=styles.page_container>
<VideoPlayer className=styles.video_container />
)
</div>
);
;
export default CallPage;
当我点击我的家将我带到 CallPage 时,这样做:
<Link
passHref
href="/callpage"
>
<Button
loadingText="Loading"
colorScheme="teal"
variant="outline"
spinnerPlacement="start"
className=styles.btn
onClick=handleAdmin
>
New Meeting
</Button>
</Link>
路由器带我去渲染除了 VideoRef 之外的所有东西。如果我刷新页面,它就会加载。你知道为什么会这样吗? devtools 抛出一个错误,表明
函数组件不能给定引用
尽管我在 DOM 元素中使用了这样的 ref,如您所见。所以我已经排除了使用forwardref。我真的很感激任何帮助。我在这里一无所知。
【问题讨论】:
函数组件不接受 React refs,您需要转发任何传递的 React refs。哪些组件将 ref 传递给它们,它是哪个 ref? 【参考方案1】:一个想法...
useEffect(() =>
if (router.isReady && myVideo.current && userVideo.current)
myVideo?.current.load();
userVideo?.current.load();
, []);
你的钩子有一个空的依赖数组。在第一次渲染时,router.isReady
肯定有可能返回 false。你应该将router.isReady
传递给依赖数组。
此外,您的 CallPage 函数有条件地运行效果。那个效果:
useEffect(() =>
if (isAdmin)
setMeetInfoPopup(true);
, []);
还应该将isAdmin
和setMeetInfoPopup
设置器的值传递到依赖数组中。假设该 setter 的 useState
值称为 meetInfoPopup
,则挂钩实际上应该是这样的:
useEffect(() =>
if (isAdmin && !meetInfoPopup)
setMeetInfoPopup(true);
, [isAdmin, meetInfoPopup]);
【讨论】:
以上是关于视频 ref 只在刷新后渲染 + 功能组件不能给 refs的主要内容,如果未能解决你的问题,请参考以下文章
ReactJs:为啥 ref.current 在渲染组件时返回 null?