【问题标题】:How to trigger an effect after an event in React如何在 React 中的事件后触发效果
【发布时间】:2019-12-18 16:16:25
【问题描述】:

我正在构建一个 React 应用程序,它基本上会加载博客文章,并为每篇文章附加 cmets。

在呈现博客文章时,也会获取该博客文章的 cmets。我还有一个允许提交评论的组件。

单击提交按钮时,我希望 cmets 刷新其数据源,并立即显示新评论。 我如何向我们的 cmets 组件发送某种事件,告诉它发送另一个获取请求?

看来这个问题的核心是这样的:

我如何习惯性地将事件发送到将触发效果的其他 React 组件?

编辑 - 一般解决方案:

  1. 将 State 提升到最近的组件中
  2. 创建一个包含状态更新函数的回调函数。将此回调作为道具传递给将触发事件的组件。当事件发生时,在处理程序中运行回调。

Post.js

import React from 'react';
import {useState, useEffect, useContext} from 'react';
import Markdown from 'markdown-to-jsx';
import Container from '@material-ui/core/Container';
import Typography from '@material-ui/core/Typography';
import SendComment from './SendComment';
import Comments from './Comments';
import {POST_URL} from './urls';
import UserContext from './UserContext';
//import CommentListContainer from './CommentListContainer';

export default function Post(props) {
  const user = useContext(UserContext);

  const [post, setPost] = useState({
    content: '',
    comments: [],
  });

  useEffect(() => {
    const UNIQUE_POST_URL = [POST_URL, props.location.state.id].join('/');

    const fetchPost = async () => {
      const result = await fetch(UNIQUE_POST_URL);
      const json = await result.json();
      setPost(json);
    };
    fetchPost();
  }, [props.location.state.id]);

  return (
    <div>
      <Container>
        <Typography
          variant="h4"
          color="textPrimary"
          style={{textDecoration: 'underline'}}>
          {post.title}
        </Typography>
        <Markdown>{post.content}</Markdown>
        {post.content.length !== 0 && (
          <div>
            <Typography variant="h4">Comments</Typography>
            <SendComment user={user} posts_id={props.location.state.id} />
            <Comments user={user} posts_id={props.location.state.id} />
          </div>
        )}
      </Container>
    </div>
  );
}

SendComment.js 组件

import React from 'react';
import TextField from '@material-ui/core/TextField';
import Grid from '@material-ui/core/Grid';
import Button from '@material-ui/core/Button';
import Paper from '@material-ui/core/Paper';
import {COMMENT_SUBMIT_URL} from './urls';

export default function SendComment(props) {
  async function handleSubmit(e) {
    const comment = document.querySelector('#comment');

    // Skip empty comments
    if (comment.value === '') {
      return;
    }

    async function sendComment(url) {
      try {
        const res = await fetch(url, {
          method: 'POST',
          body: JSON.stringify({
            comment: comment.value,
            users_id: props.user.users_id,
            posts_id: props.posts_id,
          }),
          headers: {
            Accept: 'application/json',
            'Content-Type': 'application/json',
            'Accept-Language': 'en-US',
          },
        });
        comment.value = '';
        return res;
      } catch (e) {
        console.log(e);
      }
    }
    const res = await sendComment(COMMENT_SUBMIT_URL);
    if (res.ok) {
      // Reload our comment component !
      // Here is where we want to send our "event"
      // or whatever the solution is
    }
  }

  return (
    <Grid container justify="space-evenly" direction="row" alignItems="center">
      <Grid item xs={8}>
        <TextField
          id="comment"
          fullWidth
          multiline
          rowsMax="10"
          margin="normal"
          variant="filled"
        />
      </Grid>
      <Grid item xs={3}>
        <Button variant="contained" color="primary" onClick={handleSubmit}>
          Submit
        </Button>
      </Grid>
    </Grid>
  );
}

评论.js

import React from 'react';
import {useState, useEffect} from 'react';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemText from '@material-ui/core/ListItemText';
import ListItemAvatar from '@material-ui/core/ListItemAvatar';
import Avatar from '@material-ui/core/Avatar';
import Divider from '@material-ui/core/Divider';
import {timeAgo} from './utils';
import {COMMENT_URL} from './urls';

export default function Comments(props) {
  const [comments, setComments] = useState({
    objects: [],
  });

  useEffect(() => {
    async function getComments(posts_id) {
      const filter = JSON.stringify({
        filters: [{name: 'posts_id', op: 'equals', val: posts_id}],
      });

      try {
        COMMENT_URL.searchParams.set('q', filter);

        const res = await fetch(COMMENT_URL, {
          method: 'GET',
          headers: {
            Accept: 'application/json',
            'Content-Type': 'application/json',
          },
        });
        const json = await res.json();
        setComments(json);
      } catch (e) {
        console.log(e);
      }
    }
    getComments(props.posts_id);
  }, [props.posts_id]);

  const commentList = comments.objects.map(comment => (
    <ListItem key={comment.id} alignItems="flex-start">
      <ListItemAvatar>
        <Avatar alt={comment.users.name} src={comment.users.picture} />
      </ListItemAvatar>
      <ListItemText
        primary={`${comment.users.name} - ${timeAgo(comment.created_at)}`}
        secondary={comment.comment}></ListItemText>
      <Divider />
    </ListItem>
  ));

  return <List>{commentList}</List>;
}

此代码当前有效,但是新评论仅在页面重新加载时显示,而不是在提交后立即显示。

【问题讨论】:

    标签: javascript reactjs events material-ui


    【解决方案1】:

    我认为您不能在没有任何额外逻辑的情况下发送此类事件。

    我看到的最简单的解决方案如下:一旦您拥有SendCommentComments 的父组件 (Post),您就可以将所有逻辑移入其中。除了将评论保存在SendComment 中,您还可以向它传递一个回调,该回调将在用户按下按钮时触发。然后该评论将被发送到Post 内的服务器。

    要显示 cmets,您也可以在 Post 中获取它们,然后将其作为道具传递给 Comments。像这样,您可以轻松更新 cmets,并且在用户提交新评论时不需要额外的请求。

    也更喜欢使用受控组件(SendComment 中有一个不受控制的文本字段)

    代码看起来像这样:

    Post.js

    export default function Post(props) {
      const user = useContext(UserContext);
    
      const [content, setContent] = useState('')
      const [title, setTitle] = useState('')
      const [comments, setComments] = useState([])
    
      const onNewComment = useCallback((text) => {
        // I'm not sure about your comment structure on server. 
        // So here you need to create an object that your `Comments` component 
        // will be able to display and then do `setComments(comments.concat(comment))` down below
        const comment = { 
          comment: text,
          users_id: user.users_id,
          posts_id: props.location.state.id,
        };
        async function sendComment(url) {
          try {
            const res = await fetch(url, {
              method: 'POST',
              body: JSON.stringify(comment),
              headers: {
                Accept: 'application/json',
                'Content-Type': 'application/json',
                'Accept-Language': 'en-US',
              },
            });
            return res;
          } catch (e) {
            console.log(e);
          }
        }
        const res = await sendComment(COMMENT_SUBMIT_URL);
        if (res.ok) {
          setComments(comments.concat(comment));
        }
      }, [comments]);
    
      useEffect(() => {
        const UNIQUE_POST_URL = [POST_URL, props.location.state.id].join('/');
    
        const fetchPost = async () => {
          const result = await fetch(UNIQUE_POST_URL);
          const { content, comments, title } = await result.json();
          setContent(content);
          setComments(comments);
          setTitle(title);
        };
        fetchPost();
      }, [props.location.state.id]);
    
      return (
        <div>
          <Container>
            <Typography
              variant="h4"
              color="textPrimary"
              style={{textDecoration: 'underline'}}>
              {title}
            </Typography>
            <Markdown>{content}</Markdown>
            {content.length !== 0 && (
              <div>
                <Typography variant="h4">Comments</Typography>
                <SendComment user={user} onNewComment={onNewComment} />
                <Comments user={user} comments={comments} />
              </div>
            )}
          </Container>
        </div>
      );
    }
    

    SendComment.js

    export default function SendComment(props) {
      const [text, setText] = useState('');
      const handleSubmit = useCallback(() => {
        // Skip empty comments
        if (comment.value === '') {
          return;
        }
        if(props.onNewComment) {
          props.onNewComment(text);
          setText('');
        }
      }, [props.onNewComment, text]);
    
      return (
        <Grid container justify="space-evenly" direction="row" alignItems="center">
          <Grid item xs={8}>
            <TextField
              id="comment"
              onChange={setText}
              fullWidth
              multiline
              rowsMax="10"
              margin="normal"
              variant="filled"
            />
          </Grid>
          <Grid item xs={3}>
            <Button variant="contained" color="primary" onClick={handleSubmit}>
              Submit
            </Button>
          </Grid>
        </Grid>
      );
    }
    

    评论.js

    export default function Comments(props) {
      const commentList = props.comments.map(comment => (
        <ListItem key={comment.id} alignItems="flex-start">
          <ListItemAvatar>
            <Avatar alt={comment.users.name} src={comment.users.picture} />
          </ListItemAvatar>
          <ListItemText
            primary={`${comment.users.name} - ${timeAgo(comment.created_at)}`}
            secondary={comment.comment}></ListItemText>
          <Divider />
        </ListItem>
      ));
    
      return <List>{commentList}</List>;
    }
    

    UPD:更改了一些代码以在 Post.js 中显示内容和标题

    【讨论】:

    • 太棒了,我想这就是我所做的,除了所有逻辑都向上移动到 Post 而不是 SendComment,并作为回调传递下来。这当然看起来更干净。我将把它重构为这个并尝试一下
    【解决方案2】:

    这是一个想法:

    您应该将 cmets 放在 Posts 中的状态变量上。

    就像const[comments, setComments] = useState([]);

    您可以在SendComment 中收到一个名为onCommentSent 的道具。 在您的代码中,当您发送评论时,您会执行 onCommentSent();

    因此,在 Posts 中,当发送评论时,您重新加载 cmets 的数据并使用 setComments(newData) 设置它为 comments。 当状态重新加载时,cmets 将重新获取。


    一个更好的执行想法是不要在每个评论 POST 请求上检索所有 cmets,您可以动态更新状态变量 comments 中的数据,因为知道下次您获取服务器时,评论会来。

    希望对你有帮助!

    【讨论】:

      【解决方案3】:

      我已经创建了一个可行的解决方案,但它显然很丑陋,而且绝对感觉我是在蛮力使用它,而不是按照 React 的方式来做:

      我将获取 cmets 的逻辑提取到它自己的函数中。这个函数在效果期间被调用,在 handleSubmit 函数内部。

      Comments 组件现在是 SendComment 的子组件(从组织的角度来看这没有意义)

      export default function SendComment(props) {
        const [comments, setComments] = useState({
          objects: [],
        });
      
        async function getComments(posts_id) {
          const filter = JSON.stringify({
            filters: [{name: 'posts_id', op: 'equals', val: posts_id}],
          });
      
          try {
            COMMENT_URL.searchParams.set('q', filter);
      
            const res = await fetch(COMMENT_URL, {
              method: 'GET',
              headers: {
                Accept: 'application/json',
                'Content-Type': 'application/json',
              },
            });
            const json = await res.json();
            setComments(json);
          } catch (e) {
            console.log(e);
          }
        }
      
        useEffect(() => {
          getComments(props.posts_id);
        }, [props.posts_id]);
      
        async function handleSubmit(e) {
          const comment = document.querySelector('#comment');
      
          // Skip empty comments
          if (comment.value === '') {
            return;
          }
      
          async function sendComment(url) {
            try {
              const res = await fetch(url, {
                method: 'POST',
                body: JSON.stringify({
                  comment: comment.value,
                  users_id: props.user.users_id,
                  posts_id: props.posts_id,
                }),
                headers: {
                  Accept: 'application/json',
                  'Content-Type': 'application/json',
                  'Accept-Language': 'en-US',
                },
              });
              comment.value = '';
              return res;
            } catch (e) {
              console.log(e);
            }
          }
          const res = await sendComment(COMMENT_SUBMIT_URL);
          if (res.ok) {
            getComments(props.posts_id);
          }
        }
      
        return (
          <>
            <Grid
              container
              justify="space-evenly"
              direction="row"
              alignItems="center">
              <Grid item xs={8}>
                <TextField
                  id="comment"
                  fullWidth
                  multiline
                  rowsMax="10"
                  margin="normal"
                  variant="filled"
                />
              </Grid>
              <Grid item xs={3}>
                <Button variant="contained" color="primary" onClick={handleSubmit}>
                  Submit
                </Button>
              </Grid>
            </Grid>
            <Grid container justify="space-left">
              <Grid item justify="flex-start">
                <Comments comments={comments} />
              </Grid>
            </Grid>
          </>
        );
      }
      
      

      【讨论】:

      • 请注意:最好在函数组件内部使用React.useCallback(cb, dependencies),而不是只编写函数。即使对于异步功能,您也必须执行 React.useCallback(() =&gt; (async() =&gt; { /* code goes here */ })(), []) 之类的操作,这对性能更好
      猜你喜欢
      • 2022-01-23
      • 1970-01-01
      • 2013-09-16
      • 2013-05-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-22
      相关资源
      最近更新 更多