React 17:如何在路由器标签中传递事件处理程序<LINK>



我正试图通过<LINK>标记传递一个事件处理程序。我的最终目标是将事件处理程序传递给一个子组件:

<Post post={post} onClickClose={props.onClickClose} />

因为我使用的是路由器,所以无法使用标准道具传递事件处理程序。我确信这个问题一定有解决方案,因为这似乎是一个相当正常的功能。

应用程序描述:

  1. 待办事项列表允许用户删除每个待办事项
  2. 待办事项列表允许用户导航到每个单独的帖子
  3. 每个单独的帖子都允许用户使用ToDo List可以访问的相同方法引用来删除自己

应用程序层次结构:

App
- Header
- - NotFound
- - ToDoList *
- - - Post **
- - Posts
- - - ToDoList *
- - - - ToDo **
** Ref: onClickClose -> removeTodo
* Method: removeTodo

这是我的代码的一个非常短的版本:

设置:

<!DOCTYPE HTML>
<html>
<head>

<script src="assets/babel/bbh.js"></script>
<script src="https://unpkg.com/react/umd/react.development.js" type="text/javascript"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js" type="text/javascript"></script>
<script src='https://unpkg.com/react-router-dom@4.3.1/umd/react-router-dom.js'></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/date-fns/1.30.1/date_fns.min.js" type="text/javascript"></script>

</head>
<body>

<div id="app">

<script type="text/babel" name="react-example">

import React, { Component } from 'react';
import { render } from 'react-dom';
import ReactDOM from 'react-dom';
import { welcome } from './bbh';

const Link = ReactRouterDOM.Link;
const NavLink = ReactRouterDOM.NavLink;
const Route = ReactRouterDOM.Route;
const Switch = ReactRouterDOM.Switch;
const Router = ReactRouterDOM.BrowserRouter;
const Redirect = ReactRouterDOM.Redirect;

// ToDo class component

class ToDo extends React.Component {
constructor(props) {
super(props);
}
render() {
const link = "/post/" + this.props.slug;
return (
<Link to={
{
pathname: link,
state: {
onClickClose: this.props.onClickClose
}
}
}>
My Link
</Link>
)
}
}

/*
Other class components:
App
Post
Posts
NotFound
ToDoList

*/

// Header class component

class Header extends React.Component {
constructor(props) {
super(props);
this.state = {
posts: [
{
id: 1,
slug: "hello-react",
title: "Hello React",
content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
done: true,
createdAt: dateFns.format(new Date(), "YYYY-MM-DD HH:mm:ss")
},
{
id: 2,
slug: "hello-project",
title: "Hello Project",
content: "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. ",
done: true,
createdAt: dateFns.format(new Date(), "YYYY-MM-DD HH:mm:ss")
},
{
id: 3,
slug: "hello-blog",
title: "Hello Blog",
content: "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.",
done: true,
createdAt: dateFns.format(new Date(), "YYYY-MM-DD HH:mm:ss")
},
]
};
}
render() {
return (
<Switch>
<Route exact path="/" render={() => <Posts posts={this.state.posts} />} />
<Route path="/post/:postSlug" render={(props) => {
console.log("props ",props);
const post = this.state.posts.find(
(post) => post.slug === props.match.params.postSlug
);
if (post) return 
<Post post={post} onClickClose={props.onClickClose} />
// ERROR
else return 
<NotFound />
}}
/>
<Route component={NotFound} />
</Switch>
)
}
}

render(<App />, document.getElementById('app'));

</script>

</div>

<script>
bbh.babelConfig = {
presets: ['es2015', 'react'],
plugins: ['transform-object-rest-spread'],
minified: true
} 
</script>

</body>
</html>

当我运行代码时,我会得到一个错误。

看起来我可以在链接中传递任何东西->to.state,事件处理程序除外?

我还尝试在LINK标签中创建一个标准道具,比如:

<Link onClickClose={this.props.onClickClose} to={link}>
My Link
</Link>

但是,onClickClose道具没有传递到路由器?

有人能提供一个在LINK标记中传递事件处理程序的解决方案吗?

环境:

  • Windows 10
  • 反应17
  • react路由器dom 4.3.1

注意:

我使用react router dom 4.3.1的原因是,这是唯一一个正常工作的CDN路由器版本。我正在使用CDN版本,因为我正试图在codepen.io上显示此内容

更新:

codepen.io

https://codepen.io/charles1971/pen/GRjOVzz

是的,您不能(也不应该!)将不可序列化的对象传递给React Router中的state。在浏览器中,它使用历史API,它本身要求状态是可序列化的(哪些函数不是)。

如果您试图拦截链接的行为(例如,取消对链接的导航),则可以使用onClick来完成此操作。然而,看起来更像是在不同的页面上执行操作后,试图指示代码执行某个操作。

看起来你正试图使用链接来模拟导航——为什么不使用链接和锚点,而不是使用onClick处理程序呢?例如,与其将回调作为属性传递,不如传递任务完成后下一页应该访问的URL(或对象)。

回复您的意见:

问题是,子节点在路由器的另一侧。

简而言之,不。您在这里没有定义父子关系。至少在您列出的代码中,您已经"定义了ToDo",这是一个<Link>,它将将您链接到子级。渲染树可以更准确地表示为:

<Header>
<Router>
<Route>
<Posts>
<ToDo>
<ToDo>
<ToDo>
</Posts>
</Route>
<Route>
<Post>
</Route>
</Router>
</Header>

正如您从上面的代码中看到的,您实际上并不是父子关系(尽管组件名义上是相关的),而是兄弟姐妹关系。有几种方法可以实现你想要做的事情。最简单的方法是在两个路由中都使用Links-例如,你可以在<Posts>组件中有一个到每个帖子的链接,然后在每个<Post>路由中有<Link><Posts>路由:

// In <Posts>
<ul>
{posts.map(p => <li><Link to={`/posts/${p.id}`} /></li>)}
</ul>
// In <Post>
<div>
<Link to='/posts'>Back</Link>
.... Content of the post
</div>

没有必要让这比使用简单的链接复杂得多,这就是用户期望网站无论如何都能工作的方式:)

如果你不想在<Post>中硬编码索引页的位置(这是可以理解的),你可以使用位置状态。但请记住,这会稍微模糊应用程序的流程,并且无法与常规锚点一起使用(这意味着通过书签或刷新访问您网站的用户可能会获得稍微不同的体验):

const ToDo = ({ returnURL }) => {
return <Link to={{ pathname: link, state: { returnURL  }} />
}
const Post = ({ defaultReturnURL }) => {
const loc = useLocation();
const returnURL = loc.state.returnURL ?? "/posts";
return <div>
<Link to={returnURL}>Back to Posts</Link>
....
</div>  
}

更新的PEN:

https://codepen.io/charles1971/pen/GRjOVzz

解决方案:

这个问题的解决方案是从两个不同的组件中两次引用<ToDoList />组件。

<ToDoList />组件包含removeToDo方法。不知怎的,我们需要能够在两个不同的地方使用这种方法。CCD_ 13分量需要从CCD_ 14分量和CCD_。

然后有必要在<ToDoList />组件中添加两个新道具:

新道具

来源

定义从引用组件的来源

id

识别当前活动帖子的链接

之前

<ToDoList posts="" />

之后

<ToDoList posts="" origin="" id="" />

现在,可以利用这些新道具在<ToDoList />组件的render方法中使用三元运算符。

ToDoList渲染方法

表示额外的HTML代码

render{
let todos = null;
if(this.props.origin == "posts"){
todos = this.state.todos.map(
function (todo, index) {
return (
<ToDo 
key={index} 
keyRef={index} 
ref={index} 
title={todo.title} 
done={todo.done} 
slug={todo.slug} 
onClickClose={this.removeTodo.bind(this,index,"posts")}
onClickDone={this.markTodoDone.bind(this,index)}
onClickUp={this.move.bind(this,index,"up")}
onClickDown={this.move.bind(this,index,"down")}
/>
);
}.bind(this)
);
}
else{
todos = this.state.todos.map(
function (todo, index) {
return (
<Post 
key={index} 
keyRef={index} 
ref={index} 
title={todo.title} 
done={todo.done} 
slug={todo.slug} 
createdAt={todo.createdAt} 
content={todo.content} 
id1={this.props.id} 
id2={todo.id} 
onClickClose={this.removeTodo.bind(this,index,"post")}
/>
);
}.bind(this)
);
}
return (
this.state.redirect == false ? (
this.props.origin == "posts" ? 
(
...
{todos}
...
)
:
(
...
{todos}
...
)
)
:
(
<Redirect to="/" />
)
)
}

<Post />组件中完成删除例程后,将使用重定向。这会将用户返回到路由器的ToDoList部分。显然,这看起来不太好,让用户看到路由器的空白部分,它刚刚被删除!

因此,现在<Header />组件的Switch部分看起来像:

<Switch>
<Route
exact
path="/"
render={() => <Posts posts={this.state.posts} />}
/>
<Route
path="/post/:postSlug"
render={(props) => {
const post = this.state.posts.find(
(post) => post.slug === props.match.params.postSlug
);
if (post)
return (
<ToDoList origin="post" posts={this.state.posts} id={post.id} />
);
else return <NotFound />;
}}
/>
<Route component={NotFound} />
</Switch>

而且,<Posts />组件看起来像:

class Posts extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
componentHandler.upgradeDom();
}
render() {
return <ToDoList origin="posts" posts={this.props.posts} id="" />;
}
}   

最后,<Post />组件看起来像:

class Post extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
componentHandler.upgradeDom();
}
render() {
const display = this.props.id1 == this.props.id2 ? "block" : "none";
const defaultStyle1 = {
display: display
};
const defaultStyle2 = {
padding: "20px"
};
return (
<div className="demo-card-wide mdl-card mdl-shadow--2dp" style={defaultStyle1}>
<div className="mdl-card__title post">
<h2 className="mdl-card__title-text">{this.props.title}</h2>
</div>
<div className="mdl-card__supporting-text">
{this.props.createdAt}
<i className="fa fa-trash" onClick={this.props.onClickClose}></i>
</div>
<div className="mdl-card__actions mdl-card--border">
<div className="todo-container" style={defaultStyle2}>
{this.props.content}
</div>
</div>
</div>
);
}
}

两个id道具从<ToDoList />组件发送到<Post />组件。这样可以在屏幕上显示正确的帖子。所有的帖子实际上都被渲染了,但只有一个帖子的CSSdisplay属性设置为block。其余设置为。现在,onClickClose道具[removeToDo方法的别名]可以传递给<Post />组件,该组件允许用户从路由器的帖子部分删除帖子。每个帖子也可以从路由器的ToDoList部分删除,使用相同的removeToDo方法。

最新更新