簡體   English   中英

在React中發生事件后如何觸發效果

[英]How to trigger an effect after an event in React

我正在構建一個React應用,它基本上會加載博客帖子,並為每個帖子附加評論。

呈現博客文章時,還將獲取該博客文章的評論。 我還有一個允許提交評論的組件。

單擊提交按鈕后,我希望注釋刷新其數據源,並立即顯示新注釋。 如何將某種事件發送到我們的注釋組件,以告知它發送另一個獲取請求?

看來這個問題的核心是:

如何慣用地將事件發送到其他會觸發效果的React組件?

編輯-一般解決方案:

  1. 將狀態提升到最近的部分
  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>
  );
}

Comments.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>;
}

該代碼當前有效,但是新注釋僅在重新加載頁面上顯示,而不在提交后立即顯示。

這是一個想法:

您應該在“帖子”中對狀態變量發表評論。

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

您可以在SendComment接收名為onCommentSent的道具。 在您的代碼中,發送注釋時,執行onCommentSent();

所以,在帖子中,當注釋被發送,你重裝意見的數據,並將其設置comments使用setComments(newData) 重新加載狀態后,評論將重新提取。


一個更好的性能想法是不要在每個評論POST請求上檢索所有評論,您可以動態地更新狀態變量comments的數據,因為知道下次您獲取服務器時,該評論就會到來。

希望能幫助到你!

我認為您可以在沒有任何額外邏輯的情況下發送此類事件。

我看到的最簡單的解決方案如下:一旦擁有SendCommentComments的父組件( Post ),就可以將所有邏輯移入其中。 除了將注釋保存在SendComment ,您還可以SendComment傳遞一個回調,該回調將在用戶按下按鈕時觸發。 然后將評論發送到Post內的服務器。

要顯示評論,您也可以在Post獲取評論,然后將其作為道具傳遞給Comments 這樣,您可以輕松地更新評論,並且當用戶提交新評論時,您不需要額外的請求。

也更喜歡使用受控組件( SendComment文本字段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>
  );
}

Comments.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顯示內容和標題

我已經創建了一個可行的解決方案,但是它顯然很丑陋,絕對感覺像是我在蠻力逼迫它,而不是像React那樣做:

我提取了將注釋提取到其自身函數中的邏輯。 在效果期間以及handleSubmit函數內部handleSubmit函數。

現在,“ Comments組件是SendComment的子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>
    </>
  );
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM