总结

在Workshop2当中,我们完成了这样的一个页面

image-20241129171807145

image-20241129171837195

在这个workshop3当中,我们将给这个页面添加路由和新的评论页面

image-20241129171916772

路由

我们主要用<Router /><Link />

<Router />

image-20241129172124651

  • /的是绝对路径
  • /的是相对路径

例子

image-20241129172251257

image-20241129172554545

子路由的路径是相对于其父路由的,而不是整个应用的根路径。具体来说,<DashboardHome path="/" /> 是定义在 <Dashboard> 内部的,它的路径会被解释为相对于 Dashboardpath="dashboard",而不会直接作用于全局的 /

<Link />

image-20241129172645686

网络请求

我们的最终目的:

image-20241209185655859

当前结构:

image-20241209191311326

Feed.js的Story

  • Step 0: 替换 Profile 组件为 Feed 组件
    • 将原本的 Profile 组件替换成 Feed 组件,作为展示故事的主要容器。
  • Step 1: 声明状态,使用 GET 请求加载所有评论数据
    • 在组件中声明状态,用来存储从数据库加载的评论数据。
    • 使用 GET 请求从数据库获取所有评论并更新状态。
  • Step 2: 使用 Dummy 数据渲染 SingleStory 组件
    • 使用虚拟的(Dummy)数据来测试并渲染 SingleStory 组件,模拟如何展示每一条故事。
  • Step 3: 使用 .map() 方法将故事数据(来自 GET 请求)映射为单个故事
    • 通过 .map() 方法遍历获取到的故事数据,将每个故事渲染成 SingleStory 组件。
  • Step 4: 将 NewPostInput 包裹在一个新的组件中,并添加发布功能
    • NewPostInput 组件封装到一个新的组件中,并实现发布新故事的功能,允许用户提交数据。

Card.js的Comment

  • Step 6- 8: 在每个story下面显示评论,同时添加评论框
    • image-20241209192748628

Step 1-4结果

story

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const Feed = () => {
const [stories, setStories] = useState([]);

useEffect(() => {
get("/api/stories").then((storyObjs) => {
setStories(storyObjs);
});
}, []);

let storiesList = null;
const hasStories = stories.length !== 0;
if (hasStories) {
storiesList = stories.map((storyObj) => (
<SingleStory creator_name={storyObj.creator_name} content={storyObj.content} />
));
} else {
storiesList = <div>No stories!</div>;
}

return (
<div>
<NewStory />
{storiesList}
</div>
);
// TODO (step6): use Card instead of SingleStory
};

newPostInput.js

1
2
3
4
5
6
7
8
const NewStory = () => {
const addStory = (value) => {
const body = { content: value };
post("/api/story", body);
};

return <NewPostInput defaultText="New Story" onSubmit={addStory} />;
};

目前结果:

image-20241209191517104

Step 6-9

comment

最终目的:

image-20241209190647532

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
const Feed = () => {
const [stories, setStories] = useState([]);

useEffect(() => {
get("/api/stories").then((storyObjs) => {
setStories(storyObjs);
});
}, []);

let storiesList = null;
const hasStories = stories.length !== 0;
if (hasStories) {
storiesList = stories.map((storyObj) => (
//↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓//
<Card _id={storyObj._id} creator_name={storyObj.creator_name} content={storyObj.content} />
//↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑//
//before:
//<SingleStory creator_name={storyObj.creator_name} content={storyObj.content} />
));
} else {
storiesList = <div>No stories!</div>;
}

return (
<div>
<NewStory />
{storiesList}
</div>
);
};

export default Feed;

card.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
const Card = (props) => {
const [comments, setComments] = useState([]);

useEffect(() => {
get("/api/comment", { parent: props._id }).then((commentItems) => {
setComments(commentItems);
});
}, []);

let commentsList = null;
const hasComments = comments.length !== 0;
if (hasComments) {
commentsList = comments.map((commentObj) => (
<SingleComment
_id={commentObj._id}
creator_name={commentObj.creator_name}
content={commentObj.content}
/>
));
} else {
commentsList = <div>No comments!</div>;
}

return (
<div className="Card-container">
<SingleStory
_id={props._id}
creator_name={props.creator_name}
content={props.content}
/>
{commentsList}
<NewComment storyId={props._id} />
</div>
)
// TODO (step9): use CommentsBlock
};

export default Card;

newComment

1
2
3
4
5
6
7
8
const NewComment = (props) => {
const addComment = (value) => {
const body = { parent: props.storyId, content: value };
post("/api/comment", body);
};

return <NewPostInput defaultText="New Comment" onSubmit={addComment} />;
};

image-20241209191020995

Step10

image-20241209191103251

1
2
3
4
5
6
7
8
9
const Card = (props) => {
....
return (
<div className="Card-container">
<SingleStory _id={props._id} creator_name={props.creator_name} content={props.content} />
<CommentsBlock storyId={props._id} comments={comments} addNewComment={addNewComment} />
</div>
);
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const CommentsBlock = (props) => {
return (
<div className="Card-commentSection">
<div className="story-comments">
{props.comments.map((comment) => (
<SingleComment
key={`SingleComment_${comment._id}`}
_id={comment._id}
creator_name={comment.creator_name}
content={comment.content}
/>
))}
<NewComment storyId={props.storyId} addNewComment={props.addNewComment} />
</div>
</div>
);
};

最后一步-实时评论

我们会发现,我们每次提交故事或评论的时候,都需要重新刷新才会显示出外面新添加的评论,我们要怎么修改我们的代码才能做到 我们一提交,页面就显示出新内容?

我们的stories列表是存在Feed.js当中,comment列表是存在Card.js当中,如果我们每次提交的时候都会调用他们的回调函数的话就能实时更新了

Feed.js

image-20241209195215829

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//BEFORE:
const Feed = () => {
const [stories, setStories] = useState([]);
...
let storiesList = null;
const hasStories = stories.length !== 0;
if (hasStories) {
storiesList = stories.map((storyObj) => (
<Card _id={storyObj._id} creator_name={storyObj.creator_name} content={storyObj.content} />
));
} else {
storiesList = <div>No stories!</div>;
}

return (
<div>
<NewStory />
{storiesList}
</div>
);
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//AFTER
const Feed = () => {
const [stories, setStories] = useState([]);
...
// this gets called when the user pushes "Submit", so their
// post gets added to the screen right away
const addNewStory = (storyObj) => {
setStories(stories.concat([storyObj]));
};

let storiesList = null;
const hasStories = stories.length !== 0;
if (hasStories) {
storiesList = stories.map((storyObj) => (
<Card
key={`Card_${storyObj._id}`}
_id={storyObj._id}
creator_name={storyObj.creator_name}
content={storyObj.content}
/>
));
} else {
storiesList = <div>No stories!</div>;
}
return (
<div>
<NewStory addNewStory={addNewStory} />
{storiesList}
</div>
);
};

card.js

image-20241209195105655

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//BRFORE
const Card = (props) => {
const [comments, setComments] = useState([]);
...
let commentsList = null;
const hasComments = comments.length !== 0;
if (hasComments) {
commentsList = comments.map((commentObj) => (
<SingleComment
_id={commentObj._id}
creator_name={commentObj.creator_name}
content={commentObj.content}
/>
));
} else {
commentsList = <div>No comments!</div>;
}

return (
<div className="Card-container">
<SingleStory
_id={props._id}
creator_name={props.creator_name}
content={props.content}
/>
<CommentsBlock storyId={props._id} comments={comments} />
</div>
)
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//AFTER
const Card = (props) => {
const [comments, setComments] = useState([]);
...
// this gets called when the user pushes "Submit", so their
// post gets added to the screen right away
const addNewComment = (commentObj) => {
setComments(comments.concat([commentObj]));
};

return (
<div className="Card-container">
<SingleStory _id={props._id} creator_name={props.creator_name} content={props.content} />
<CommentsBlock storyId={props._id} comments={comments} addNewComment={addNewComment} />
</div>
);
};

NewPostInput.js

image-20241209195111607

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//BEFORE
/**
* New Story is a New Post component for stories
*/
const NewStory = () => {
const addStory = (value) => {
const body = { content: value };
post("/api/story", body);
};

return <NewPostInput defaultText="New Story" onSubmit={addStory} />;
};

/**
* New Comment is a New Post component for comments
*
* Proptypes
* @param {string} storyId to add comment to
*/
const NewComment = (props) => {
const addComment = (value) => {
const body = { parent: props.storyId, content: value };
post("/api/comment", body);
};

return <NewPostInput defaultText="New Comment" onSubmit={addComment} />;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//AFTER
/**
* New Story is a New Post component for stories
*/
const NewStory = (props) => {
const addStory = (value) => {
const body = { content: value };
post("/api/story", body).then((story) => {
// display this story on the screen
props.addNewStory(story);
});
};

return <NewPostInput defaultText="New Story" onSubmit={addStory} />;
};

/**
* New Comment is a New Post component for comments
*
* Proptypes
* @param {string} storyId to add comment to
*/
const NewComment = (props) => {
const addComment = (value) => {
const body = { parent: props.storyId, content: value };
post("/api/comment", body).then((comment) => {
// display this comment on the screen
props.addNewComment(comment);
});
};

return <NewPostInput defaultText="New Comment" onSubmit={addComment} />;
};