首页 > 解决方案 > 如何在没有 fowardRef 的情况下从父级访问子级引用

问题描述

我有一个适用于父组件的 ref,但我需要在子组件上使用这个 ref。

const divRef = React.useRef<any>();

但是props.ref在commentListContainer 上显示未定义。我应该传递什么commentListContainer

我看到 forwardRef 可以在这样的情况下使用,但我不确定这个 fowardRef 将如何以打字稿方式使用钩子工作。

PostItemContainer.tsx

import React, { Fragment, useState, useRef } from "react";
import Avatar from "@material-ui/core/Avatar";
import Button from "@material-ui/core/Button";
import Grid from "@material-ui/core/Grid";
import Paper from "@material-ui/core/Paper";
import Typography from "@material-ui/core/Typography";
import DeleteOutlineOutlinedIcon from "@material-ui/icons/DeleteOutlineOutlined";
import FavoriteIcon from "@material-ui/icons/Favorite";
import FavoriteBorderIcon from "@material-ui/icons/FavoriteBorder";
import moment from "moment";
import { toast, ToastContainer } from "react-toastify";
import OurLink from "../../../common/OurLink";
import CommentForm from "../comment/CommentForm";
import CommentList from "../commentList/CommentList";
import OurModal from "../../../common/OurModal";
import "react-toastify/dist/ReactToastify.css";

function PostItemContainer(props: any) {
    const [openModal, setOpenModal] = useState(false);
    const [openForm, setOpenForm] = useState(false);
    const [comment_body, setCommentBody] = useState("");
    const [gifUrl, setGifUrl] = useState("");
    const divRef = React.useRef<any>();
    const writeComment = () => {
        // this is the same as this.setState({ openForm: !this.state.open })
        setOpenForm(!openForm);
    };

    const commentChange = (comment) => {
        setGifUrl("");
        setCommentBody(comment);
    };
    const selectGif = (e) => {
        setGifUrl(e.images.downsized_large.url);
        setCommentBody("");
        // you wont be able to add text comment with a gif, it will look weird :(
    };
    const handleClickOpen = () => {
        setOpenModal(true);
    };
    const handleCloseModal = () => {
        setOpenModal(false);
    };
    const commentSubmit = (e: any, id: number) => {
        e.preventDefault();
        const formData = {
            comment_body,
            id,
            gifUrl,
        };
        props.postComment(formData);
        setCommentBody("");
        setOpenForm(false);
        console.log(divRef);
        window.scrollTo(0, divRef.current.offsetTop);
    };
    const { post, currentUser, getNotifications } = props;

    return (
        <Fragment>
            {getNotifications && <ToastContainer autoClose={1000} position={toast.POSITION.BOTTOM_RIGHT} />}
            <Grid item={true} sm={12} md={12} style={{ margin: "20px 0px" }}>
                <Paper style={{ padding: "20px" }}>
                    <Typography variant="h5" align="left">
                        <OurLink
                            style={{ fontSize: "16px" }}
                            to={{
                                pathname: `/post/${post.id}`,
                                state: { post },
                            }}
                            title={post.title}
                        />
                    </Typography>
                    <Grid item={true} sm={12} md={12} style={{ padding: "30px 0px" }}>
                        <Typography align="left">{post.postContent.slice(0, 50)}</Typography>
                    </Grid>
                    <Avatar
                        style={{
                            display: "inline-block",
                            margin: "-10px -20px",
                            padding: "0px 30px 0px 20px",
                        }}
                        sizes="small"
                        src={post.author.gravatar}
                    />
                    <Typography display="inline" variant="subtitle1" align="left">
                        <OurLink
                            to={{
                                pathname: `/profile/${post.author.username}`,
                                state: { post },
                            }}
                            title={post.author.username}
                        />
                    </Typography>
                    <Typography align="right">Likes: {post.likeCounts}</Typography>
                    <Grid container={true} spacing={1} style={{ padding: "20px 0px" }}>
                        <Grid item={true} sm={10} lg={10} md={10} style={{ padding: "0px 0px" }}>
                            <Typography align="left">
                                {currentUser && currentUser.user && post.userId === currentUser.user.id ? (
                                    <span style={{ cursor: "pointer" }} onClick={() => props.deletePost(post.id, post.userId)}>
                                        <DeleteOutlineOutlinedIcon style={{ margin: "-5px 0px" }} color="primary" /> <span>Delete</span>
                                    </span>
                                ) : null}
                            </Typography>
                        </Grid>
                        <Grid item={true} sm={2} lg={2} style={{ padding: "0px 15px" }}>
                            <Typography align="right">
                                {Object.entries(currentUser).length === 0 ? (
                                    <Fragment>
                                        <span onClick={handleClickOpen}>
                                            <FavoriteBorderIcon style={{ color: "red", cursor: "pointer" }} />
                                        </span>
                                        {openModal ? <OurModal open={openModal} handleClose={handleCloseModal} /> : null}
                                    </Fragment>
                                ) : (
                                    <Fragment>
                                        {post.likedByMe === true ? (
                                            <span style={{ cursor: "pointer" }} onClick={() => props.dislikePost(post.id)}>
                                                <FavoriteIcon style={{ color: "red" }} />
                                            </span>
                                        ) : (
                                            <span onClick={() => props.likePost(post.id)}>
                                                <FavoriteBorderIcon style={{ color: "red", cursor: "pointer" }} />
                                            </span>
                                        )}
                                    </Fragment>
                                )}
                            </Typography>
                        </Grid>
                    </Grid>

                    <Typography variant="h6" align="left">
                        {moment(post.createdAt).calendar()}
                    </Typography>
                    <Grid item={true} sm={12} lg={12} style={{ paddingTop: "40px" }}>
                        {Object.entries(currentUser).length === 0 ? (
                            <Fragment>
                                <Button onClick={handleClickOpen} variant="outlined" component="span" color="primary">
                                    {"Write A Comment"}
                                </Button>
                                {openModal ? <OurModal open={openModal} handleClose={handleCloseModal} /> : null}
                            </Fragment>
                        ) : (
                            <Fragment>
                                <Button onClick={writeComment} variant="outlined" component="span" color="primary">
                                    {openForm ? "Close" : "Write A Comment"}
                                </Button>
                            </Fragment>
                        )}
                        {openForm ? (
                            <CommentForm
                                commentChange={(e: any) => commentChange(e.target.value)}
                                comment_body={comment_body}
                                onSubmit={(e) => commentSubmit(e, post.id)}
                                gifUrl={selectGif}
                                isGif={gifUrl}
                            />
                        ) : null}

                        {post.Comments.length > 0 ? (
                            <Fragment>
                                <Typography style={{ padding: "10px 0px", margin: "20px 0px" }}>Commments</Typography>
                                <CommentList ref={divRef} user={currentUser} deleteComment={props.deleteComment} userId={post.userId} postId={post.id} comments={post.Comments} {...props} />

                                {/*  if show more hide show more button and show show less comments button */}
                            </Fragment>
                        ) : (
                            <Grid item={true} sm={12} lg={12} style={{ padding: "30px 0px" }}>
                                <Typography>No Commments Yet</Typography>
                            </Grid>
                        )}
                       // <div ref={divRef}></div> works here 
                    </Grid>
                </Paper>
            </Grid>
        </Fragment>
    );
}
export default PostItemContainer;

评论列表容器.tsx

import React from "react";
import List from "@material-ui/core/List";
import Typography from "@material-ui/core/Typography";
import CommentItem from "./../commentItem/CommentItem";
import moment from "moment";
import CommentAuthorData from "../commentAuthorData/commentAuthorData";
const ourStyle = {
    margin: "15px",
};
const CommentListContainer = (props) => {
    const { comment, openModal, handleClickOpen, handleCloseModal, isBold } = props;
    return (
        <List style={{ paddingBottom: "20px" }}>
            <CommentAuthorData {...props} comment={comment} openModal={openModal} handleClickOpen={handleClickOpen} handleCloseModal={handleCloseModal} isBold={isBold} />

            {/* want to call ref here but it returns undefined */}
            <div ref={props.ref} style={ourStyle}>
                <CommentItem comment={comment} user={props.user} postId={props.postId} {...props} />
                <Typography style={{ fontSize: "12px" }} variant="body1" align="left">
                    {moment(comment.createdAt).calendar()}
                </Typography>
            </div>
        </List>
    );
};

export default CommentListContainer;

标签: reactjstypescript

解决方案


根据文档:https://reactjs.org/docs/forwarding-refs.html您应该为此使用 forwardRef :

// I used any for props, feel free to replace with your Props interface 
const CommentListContainer: React.ForwardRefRenderFunction <HTMLDivElement, any> = (props, ref) => {
    const { comment, openModal, handleClickOpen, handleCloseModal, isBold } = props;
    return (
        <List style={{ paddingBottom: "20px" }}>
            <CommentAuthorData {...props} comment={comment} openModal={openModal} handleClickOpen={handleClickOpen} handleCloseModal={handleCloseModal} isBold={isBold} />

            {/* here you pass your ref */}
            <div ref={ref} style={ourStyle}>
                <CommentItem comment={comment} user={props.user} postId={props.postId} {...props} />
                <Typography style={{ fontSize: "12px" }} variant="body1" align="left">
                    {moment(comment.createdAt).calendar()}
                </Typography>
            </div>
        </List>
    );
};

// you use forwardRef here
export default React.forwardRef(CommentListContainer);

这是相关的 TS 定义:https ://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react/index.d.ts#L564

你的父母应该保持不变。


推荐阅读