【问题标题】:pass props to a component outside render function将 props 传递给渲染函数之外的组件
【发布时间】:2020-02-25 04:52:02
【问题描述】:

我有一个组件,Tracks,它列出了歌曲和歌曲艺术家。艺术家名称是将视图带到 ArtistDetail 组件的链接。像这样:

https://imgur.com/a/SfDqONw

我希望这样当用户点击艺术家姓名时,他们将被带到将显示“艺术家是 {name of artist clicked}”的艺术家详细信息组件。但正如您在上面的 sn-p 中看到的那样,艺术家姓名没有出现。当我在艺术家详细信息组件中进行一些安慰时,它说道具名称未定义

import React, { Component } from "react";
import SpotifyWebAPI from "spotify-web-api-js";
import { ArtistDetail } from "./ArtistDetail";
const spotifyApi = new SpotifyWebAPI();

export class Tracks extends Component {
  constructor(props) {
    super(props);
    this.state = {
      currentArtist: "lala"
    };
  }

  openArtist = name => {
    this.setState({
      currentArtist: name
    });
    const artist = <ArtistDetail name={currentArtist}/>
     //even with const artist = <ArtistDetail name="francis"/> it says undefined

    this.props.switchView("ADetail");//this function is in change of changing the view from component to component.

    return artist;// it does not seem to return the prop to the component
  };

  render() {
    const { currentArtist } = this.state;
    return (
      <div>
                  <TableCell>
                    <Link
                      onClick={() => {
                        this.openArtist(artists.name);
                      }}
                    >
                      {console.log("the current artist")}
                      {console.log(currentArtist)}
                      {artists.name}
                    </Link>
        </div>
      </div>
    );
  }
}

export default Tracks;

这里是 ArtistDetail.js 组件

export const ArtistDetail = (props) => {
    console.log("the prop name")
    console.log(props.name);
    return (
        <div>
            <h1> The artist is </h1>
            <h1>{props.name}</h1>
        </div>
    )
}

我只是想在点击时将道具传递给组件。单击时,艺术家详细信息应获取艺术家的名称作为道具,然后 switchView 会将显示的组件从轨道切换到艺术家详细信息。 switchView 完成了它的工作。但是这个名字似乎并没有到达artistDetail。根据我的阅读,您似乎不能在渲染方法之外调用组件。但是如果没有 openArtist 方法,我不知道如何获得艺术家细节的道具

我尝试了这个内部轨道渲染方法:

{currentArtist != "lala" ? (<ArtistDetail name="francis"/>) && (this.props.switchView("ADetail"))

它仍然无法显示弗朗西斯

【问题讨论】:

    标签: reactjs


    【解决方案1】:

    React 根据 render 中发生的情况更新 DOM 及其数据。它并不关心你的组件做什么。它只关心你渲染的内容。在render 之外创建的任何组件都不会被放入它跟踪的树中。

    那么,如何更新渲染的组件?通过更新数据,导致重新渲染。

    React 被设计为在渲染时向下流动数据。父级可能会更新其子级,但子级不会更新父级、兄弟级或除自身及其子级之外的任何内容。这就是让所有魔法在render() 内部发生的原因。因为render 关注的是组件及其子组件发生了什么,它只需要关心它的状态和道具如何影响下游组件,而不用担心上游的东西。

    这对于渲染来说很好,但是如果下游组件让用户更改上游的东西会发生什么?

    这就是消息传递的用武之地。当数据流向下游时,消息流向上游。这个想法是渲染是一个自上而下的过程,但交互是一个自下而上的过程。

    关键是在这两种情况下,流量都会在树的树枝上上下流动。数据流向孩子,消息流向父母。如果我们尝试向被删除的阿姨和表亲发送消息数十次,我们很快就会忘记谁负责什么。通过向上发送消息以向下传输数据,我们始终可以查找树以找到我们的事实来源。

    有一些不同的消息传递方式。

    • handler props 这是访问父数据的最紧密耦合的方式。它涉及在父组件中创建一个更新其状态的函数,然后将该函数作为道具传递给子组件。孩子用更新的信息调用它,父母更新它的状态。更新状态告诉 React 用新状态重新渲染那个父级(以及它的子级)。受控表单输入执行此操作。但是,对于处理多层次的组件或复杂的数据结构是不切实际的。

    • useReducer() React 16.8 引入了 Hooks,它为函数式组件添加了很多功能。 useReducer 是一个特别有用的钩子,用于管理跨多个组件的更复杂的状态,例如表单数据。 useReducer 不是为每个被管理的状态向下钻取一个方法,而是返回一个 dispatch 方法,该方法可以被不同的对象调用,这些对象以不同的方式影响状态。 dispatch({type: 'foo', value:'bar'}) 可能会更改此useReducer 实例管理的状态中的“foo”值,而dispatch({type: 'increment'}) 可能会更新同一状态对象的另一个成员中的计数器。通过将dispatch 从子级传递到子级,您可以使用它来告诉状态从需要该能力的任何子级进行更改,并且祖先组件将获取消息以更新其状态并重新呈现自身及其后代。

    • 全局状态管理器 诸如 Redux 和 MobX 之类的包的作用类似于 useReducer,但规模更大。它们提供了在您的应用程序顶部创建全局状态的方法,但不必将调度函数一直钻到您需要的位置,它们允许您选择哪些组件可以发送消息和接收新数据。他们还拥有庞大的插件生态系统来扩展其功能。

    对于您所描述的,我可能会选择全局状态管理器。查看您的 GIF,看起来您最终会得到一个非常大的数据集,并且全局管理器有办法简化您需要了解给定组件中的哪些状态。此外,如果您的详细视图位于与列表视图完全不同的分支中,则需要大量钻取才能从列表深处一直到带有函数或useReducer 的一侧获取消息。全球经理们规避了这一点。

    例如,如果您使用 Redux,您的状态会类似于

    {
      currentArtist: null,
      currentView: 'tracks',
      artists: [
        {...artist1Data},
        {...artist2Data},
        ...
      ]
    }
    

    此状态将在您的应用程序的最顶部定义,如果需要,每个组件都可以使用。

    然后,您将拥有一个名为 reducer 的函数, 该函数接收消息并根据该消息的要求更新状态。 Redux 然后获取你的初始状态和你的 reducer,并让你访问状态和调度方法,类似于 useReducer 所做的。

    不必深入到嵌套的组件,对于类,您可以将该组件包装在 Redux 的高阶组件中,或者对于功能组件,您可以通过 useSelector()useDispatch() 访问状态和调度钩子。

    因此,在这种状态下,不是在 props 中传递 switchView 方法,而是持有不同视图的根的顶级组件将访问 Redux 状态,检查 currentView 成员,并显示该视图.当 Track 组件需要切换到艺术家详细视图时,它会访问 Redux 的 dispatch 方法并向 Redux 发送一个 action,可能类似于 dispatch({type: 'switchView', view: 'detail'})。您的 reducer 识别 'switchView' 操作类型并将顶级全局状态的 currentView 成员更新为 'detail'。

    由于我们已经连接了视图切换组件,它知道 Redux 状态何时发生变化。它发现当前视图现在应该是“详细信息”,并重新渲染以显示该视图,而不是“轨道”视图。

    缺少的部分正在更新艺术家。这几乎与更新视图相同。在我们更新视图之前,我们调度另一个动作,例如dispatch({type: 'setCurrentArtist', artist: name})。这一次,reducer 看到 'setCurrentArtist' 并且知道将 currentArtist 成员设置为在 artist 中传递的值。然而,我们没有创建一个新的 ArtistDetail 组件,而是在细节视图中已经有了一个现有的 ArtistDetail 组件。当我们将它连接到 Redux 状态时,它可以识别 currentArtist 何时发生变化,然后在全局状态值发生变化时重新呈现自己。

    这是改变 React 应用程序对用户的显示方式的关键。要更新演示文稿,我们不能修改现有组件。一旦渲染发生,它就会被发送到野外,再也不会被看到。相反,我们通过更新当前组件中的 props 和状态,或通过消息传递祖先组件中的数据更改来触发重新渲染。基本上,每次我们希望分支看起来不同时,我们都会通过更改数据来重新渲染整个分支。

    这听起来非常低效,但 React 的设计目的是非常快速地做到这一点。如果它开始感觉迟钝,它会为您提供缓存其中一些内容的方法,但通常如果组件本身渲染复杂,而不是因为组件的原始数量,就会发生这种情况。

    【讨论】:

    • 所以在处理道具的情况下,我将在轨道中创建一个函数,然后将该函数作为道具传递给艺术家细节。当你说孩子会用更新的信息打电话给它时,你是什么意思?更新的信息是来自孩子还是家长?
    • 哦!关键是该函数不是在inside Tracks 中创建的。它需要从包含 Tracks 和原始 ArtistDetail 的任何祖先传下来。在 Tracks 中调用,但在 Dashboard 中定义。
    【解决方案2】:

    我的错,我错过了关于弗朗索瓦的评论。进一步看,这里有一些东西......

    ArtistDetail 正在事件处理程序中创建。它被返回给 onClick,然后消失,因为事件处理程序不应该返回任何东西。

    如果你想改变一个特定的元素,你必须在它的现有位置改变它的 props,或者在它的父元素中替换它。我强烈建议更改道具而不是创建新道具,因为大规模创建新组件会很昂贵。

    如果数据位于树中完全不同的位置,请考虑使用 useReducer 或全局状态管理器管理状态,并调度从顶部向下流动的更新。这就是 React 的核心原则:数据向下流动。

    【讨论】:

    • 我不明白你的意思。现在艺术家细节甚至没有得到“弗朗西斯”作为道具。 currentArtist 目前不应该在渲染函数中做任何事情吗?它在 openArtist 函数中更新。为什么渲染中的currentArtist需要绑定对象,那是什么对象。谢谢
    • 哈哈是的,后来我看到了。对不起!当我进一步查看时,我重写了。
    • 是的,这就是我想要做的,在artistdetail 中更改prop.name,因为它正在使用轨道中的onclick 功能进行更新。我不明白。好的,你能解释为什么它不承认弗朗西斯吗?也许我能理解。对不起,我在哪里创建一个新道具。谢谢
    • 好的,所以你正在做的是创建一个新的 ArtistDetail 组件......但你没有把它放到你的应用程序中。它正在被创建,然后立即被丢弃。我猜你的其他视图中有一个默认的 ArtistDetail 组件?这就是您所看到的,而不是这个新组件。为了使此更新可见,您必须将新实例嵌入到另一个视图中,或者更新该视图中已有的实例。我将添加一个新答案来解释 React 如何期望更新发生背后的想法。
    • 好的,我试过这个:{currentArtist != "lala" ? () && (this.props.switchView("ADetail")) : null} 在轨道渲染方法中,它仍然无法显示 francis
    【解决方案3】:

    好的,所以查看您的 Dashboard 组件,看起来switchView 来自 Dashboard 上方的组件。当它被调用时,我假设它会更新该父组件的状态。更新组件的状态会告诉 React 重新调用该组件的 render 方法,重新渲染它自己及其子组件。

    问题是 Dashboard 渲染了适当的孩子,但没有为他们填充任何道具。我猜 Home 和 Tracks 有不需要道具的默认演示文稿,但 ArtistDetail 没有任何可用的东西。

    当您进入 Tracks 时,您已经超出了 Dashboard 的范围,因此 Dashboard 不知道 Tracks 在做什么。在 Tracks 下创建一个新的 ArtistDetail 将无济于事。您需要使当前艺术家可用于 Dashboard,以便它可以将艺术家插入它可以看到的 ArtistDetail。

    执行此操作的一种方法是执行与更新当前视图相同的操作——创建一个函数来更新仪表板中的currentArtist 状态。将该状态传递给 Dashboard 的 ArtistDetail 组件。将功能传递给您的链接,并通过该功能发送新的当前艺术家。发送新艺术家会更新 Dashboard 的状态,并重新渲染新的 currentArtist 并传递给 ArtistDetail。

    export const Dashboard = ({ currentView, switchView}) => {
    
      // Get the current artist from state and a setter to update the state.
      // We set the state to "" as an initial value.
      const [currentArtist, setCurrentArtist] = useState("");
    
      return (
        <div >
          {currentView == "Home" && <Home/>}
          {currentView == "Tracks" && <Tracks switchView={switchView} currentView={currentView} setCurrentArtist={setCurrentArtist}/>} // make "setCurrentArtist" available to Tracks
          {currentView == "SearchE" && <SearchE/>}
          {currentView == "Popular" && <Popular/>}
          {currentView == "ADetail" && <ArtistDetail name={currentArtist} />}//when i do switchView("ADetail") in tracks, it switches to the artist view
        </div>
      );
    };
    

    只要 Tracks 中的链接在调用 this.props.switchViews 之前调用 this.props.setCurrentArtist,就会填充 currentArtist 状态并准备好切换。

    这是我在下面描述的处理程序道具模式的示例。

    1. 祖先传下来一个函数。
    2. 后代通过函数向上传递消息。
    3. 祖先中的状态发生了变化。
    4. React 看到祖先的状态发生了变化,并调用了祖先的 render 函数。
    5. 祖先使用更新的数据呈现自己及其后代。

    希望这有助于了解消息如何更新祖先数据以更新子代。

    当然,现在您已经在应用中传递了两个函数。想象一下当您散布更多功能时的感觉!功能道具适用于一些功能,但在某些时候,我的其他答案中描述的其他方法之一可能更容易管理。

    【讨论】:

    • 哦保佑你保佑你保佑你。有效。我真的还不想使用 Redux。我需要为此进入头部空间,而我还没有。稍后我将使用 redux 更新应用程序。非常感谢你的耐心,并真的回来看我的漫无边际。我希望我能给你一个礼品篮。谢谢你的好意,我希望人们对你也一样。
    【解决方案4】:

    @迈克尔 当我尝试这个时:

    {currentArtist != "lala" ? (<ArtistDetail name ="francis" }/> ) : null} 
    

    prop 被定义为 francis,但是当 switchView 发挥作用时,prop 突然未定义?为什么?所以当我想到它时,switchView 逻辑会显示原始艺术家的详细信息。那没什么。在我的仪表板中:

    export const Dashboard = ({ currentView, switchView}) => {
      return (
        <div >
          {currentView == "Home" && <Home/>}
          {currentView == "Tracks" && <Tracks switchView={switchView} currentView={currentView}/>}
          {currentView == "SearchE" && <SearchE/>}
          {currentView == "Popular" && <Popular/>}
          {currentView == "ADetail" && <ArtistDetail/>}//when i do switchView("ADetail") in tracks, it switches to the artist view
        </div>
      );
    };
    

    所以我想也许这就是它不起作用的原因,即使更新了道具,上面的逻辑也只显示原始艺术家视图。但是如果是这样的话,当我调用 siwtchView("whatever page") 时,其他视图不应该只显示页面的原始状态(即什么都没有)

    【讨论】:

    • 啊啊啊!好的,这是有道理的。哈哈 有时间再回答了。我可能需要一点时间才能坐下来。
    • 我猜 Home 和 Tracks 不需要任何额外的属性来完成它们的工作?
    猜你喜欢
    • 2019-12-05
    • 2016-03-19
    • 2018-05-05
    • 2021-04-15
    • 2019-07-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多