【问题标题】:React Child component will mount then unmount after calling parent function to set parent stateReact Child 组件将在调用父函数设置父状态后挂载然后卸载
【发布时间】:2017-10-06 19:30:04
【问题描述】:

我有两个组件,父母和孩子。父级跟踪音频播放器组件(子级是播放器)以及播放器正在播放的片段,例如第 1 段可能是前 34 秒,然后是第二段,直到 215 秒,依此类推。

我的父组件渲染播放器组件并将绑定函数传递给播放器,以便播放器可以使用播放器的当前时间更新父组件,以便父组件可以确定应该突出显示哪个片段。

问题是 (1)(主要问题)一旦播放按钮被点击并且它播放,或者用户跳过,超过第一个分段中断,然后父级的状态会更新,但播放器被卸载,导致 MediaElement 被移除; (2)(小问题)初次加载页面时,Player卸载,然后是父级挂载,接着是Player卸载再挂载。我相信它们是相关的。

父母:

import React from 'react'
import shortid from 'shortid'

import Frame from '../../layout/Frame'
import Box from '../../layout/Box'
import Flex from '../../layout/Flex'
import G1 from '../../layout/G1'

import Player from '../../parts/Player'

import BriefingTitle from './BriefingTitle'

import {assoc, lensPath, set, view} from 'ramda'
import {createMarkup} from '../../../lib/tools'

class Briefing extends React.Component {
  constructor({briefing}) {
    super()

    const segments = briefing.segments.map(assoc('playing', false))
    console.log('segments:', segments)
    this.state = {
      briefing,
      segments
    }
    this.parentMonitor = this.updateSegments.bind(this)
  }

  updateSegments(time) {
    console.log('time:', time)
    const firstPlayingLens = lensPath([0, 'playing'])
    if (time > 36 && !view(firstPlayingLens, this.state.segments)) {
      this.setState(set(firstPlayingLens, true, this.state.segments))
    }
  }

  componentDidMount() {
    console.log('Briefing mounted')
  }

  componentWillUnmount() {
    console.log('Briefing will unmount')
  }

  render() {
    const {briefing, segments} = this.state
    return (
      <Frame pb={['0px', 3]}>
        <G1>
          <Flex pt={[2, 3]} direction={['column', 'row']}>
            <Box mt={[2, 'm']} mr={2} shrink={0} grow={2} order={[2, 1]}>
              <BriefingTitle><span dangerouslySetInnerHTML={createMarkup(briefing.title)} /></BriefingTitle>

              <Box mt={0} pt={0} bt>
                <Player key={'briefing_'+briefing.id} url={briefing.audioFile} type="audio/mp3" duration={briefing.duration} parentMonitor={this.parentMonitor}>Play Full Episode</Player>
              </Box>
              <Box mt={0} pt={0} bt>
                {briefing.segments.map(s => s.playing ? <p><strong>{s.title}</strong></p> : <p>{s.title}</p>)}
              </Box>
            </Box>
          </Flex>
        </G1>
      </Frame>
    )
  }
}

export default Briefing

玩家:

import React from 'react'
import styled from 'styled-components'

import Flex from '../../layout/Flex'
import Box from '../../layout/Box'

import 'mediaelement'
import 'mediaelement/build/mediaelementplayer.min.css'
import 'mediaelement/build/mediaelement-flash-video.swf'
import 'mediaelement-plugins/dist/skip-back/skip-back.min.js'
import 'mediaelement-plugins/dist/skip-back/skip-back.css'

import {rem} from '../../../lib/tools'
import {type} from '../../../designSystem'

const StyledSpan = styled.span`
  font-family: ${type.family.default};
  font-size: ${rem(type.size.s0)};
  font-weight: ${type.weight.bold};
  line-height: ${type.lineHeight.meta};
`

class Player extends React.Component {
  constructor(props, {
    inverse = props.inverse ? true : false
  }) {
    super()
    this.state = {
      inverse,
      children: props.children,
      player: null
    }
  }

  monitor(media) {
    this.props.parentMonitor(media.getCurrentTime())
    setTimeout(this.playing.bind(this), 200)
  }

  playing() {
    this.monitor(this.state.player)
  }

  success(media, node, instance) {
    // successfully loaded!
    const playEvent = e => this.playing()
    media.addEventListener('playing', playEvent)
    media.removeEventListener('pause', playEvent)
    media.removeEventListener('ended', playEvent)
  }

  error(media) {
    // failed to load
  }

  componentDidMount() {
    console.log('Player mounted')
    const {MediaElementPlayer} = global
    if (MediaElementPlayer) {
      const options = {
        features: ['skipback'],
        useDefaultControls: true,
        pluginPath: './build/static/media/',
        skipBackInterval: 31,
        skipBackText: 'Rewind 30 seconds',
        success: (media, node, instance) => this.success(media, node, instance),
        error: (media, node) => this.error(media, node)
      }
      this.setState({player: new MediaElementPlayer('player_'+this.props.key, options)})
    }
  }

  componentWillUnmount() {
    console.log('Player will unmount')
    if (this.state.player) {
      this.state.player.remove()
      this.setState({player: null})
    }
  }

  shouldComponentUpdate() {
    return false
  }

  render() {
    return (
      <Flex justify={this.state.children ? 'space-between' : ''} align="center">
        <Flex align="center">
          <audio id={'player_'+this.props.key} width={this.props.width || 400}>
            <source src={this.props.url} type={this.props.type} />
          </audio>
        </Flex>
      </Flex>
    )
  }
}

export default Player

我正在使用 MediaElement 和 React 15.5.4。

【问题讨论】:

  • 对于简报组件,请尝试将播放器的键设置为:&lt;Player key="MyPlayer" ... /&gt;
  • 谢谢@Hoyen。我添加了它,它可能已经解决了小问题,但主要问题,即正在卸载的播放器,仍然存在。我会根据您的建议更新问题。
  • 当你setState() 时,它会导致整个组件重新渲染。这可能就是它正在卸载的原因。渲染组件时是否需要segments?如果没有,请实现 shouldComponentUpdate() 函数,如果您不希望它重新渲染,则让它返回 false。
  • 我确实需要更新显示以显示正在播放的片段。我尝试将shouldComponentUpdate 添加到播放器,但它仍然未安装。我将更新我的问题以反映父显示上的分段更改。

标签: javascript reactjs mediaelement.js


【解决方案1】:

在@Hoyen 的帮助下确定重新渲染是由父级的状态更改引起的,我发现我需要将父级的state 与段的state 分开。我将这些片段放在它们自己的子类中,并在玩家时间更新时从父类中调用它们。

注意(在父级中)对 Segment 子级 this.refs.segments.updateSegments 的调用和 Segments 的父级渲染中的 ref 属性 &lt;Segments ref="segments" key={"bSegments"+this.state.briefing.id} segments={segments}&gt;&lt;/Segments&gt; 可以调用子组件。

家长:

import React from 'react'
import shortid from 'shortid'

import Frame from '../../layout/Frame'
import Box from '../../layout/Box'
import Flex from '../../layout/Flex'
import G1 from '../../layout/G1'

import Player from '../../parts/Player'
import Segments from '../../parts/Player/Segments'

import BriefingTitle from './BriefingTitle'

import {assoc, lensPath, set, view} from 'ramda'
import {createMarkup} from '../../../lib/tools'

class Briefing extends React.Component {
  constructor({briefing}) {
    super()

    const segments = briefing.segments.map(assoc('playing', false))
    console.log('segments:', segments)
    this.state = {
      briefing,
      segments
    }
    this.parentMonitor = this.updateSegments.bind(this)
  }

  updateSegments(time) {
    this.refs.segments.updateSegments(time)
  }

  componentDidMount() {
    console.log('Briefing mounted')
  }

  componentWillUnmount() {
    console.log('Briefing will unmount')
  }

  render() {
    const {briefing, segments} = this.state
    console.log('render Briefing')
    return (
      <Frame pb={['0px', 3]}>
        <G1>
          <Flex pt={[2, 3]} direction={['column', 'row']}>
            <Box mt={[2, 'm']} mr={2} shrink={0} grow={2} order={[2, 1]}>
              <BriefingTitle><span dangerouslySetInnerHTML={createMarkup(briefing.title)} /></BriefingTitle>

              <Box mt={0} pt={0} bt>
                <Player key={'briefing_'+briefing.id} url={briefing.audioFile} type="audio/mp3" duration={briefing.duration} parentMonitor={this.parentMonitor}>Play Full Episode</Player>
              </Box>
              <Segments ref="segments" key={"bSegments"+this.state.briefing.id} segments={segments}></Segments>
            </Box>
          </Flex>
        </G1>
      </Frame>
    )
  }
}

export default Briefing

玩家:

import React from 'react'
import styled from 'styled-components'

import Flex from '../../layout/Flex'
import Box from '../../layout/Box'

import 'mediaelement'
import 'mediaelement/build/mediaelementplayer.min.css'
import 'mediaelement/build/mediaelement-flash-video.swf'
import 'mediaelement-plugins/dist/skip-back/skip-back.min.js'
import 'mediaelement-plugins/dist/skip-back/skip-back.css'

import {rem} from '../../../lib/tools'
import {type} from '../../../designSystem'

const StyledSpan = styled.span`
  font-family: ${type.family.default};
  font-size: ${rem(type.size.s0)};
  font-weight: ${type.weight.bold};
  line-height: ${type.lineHeight.meta};
`

class Player extends React.Component {
  constructor(props, {
    inverse = props.inverse ? true : false
  }) {
    super()
    this.state = {
      inverse,
      children: props.children,
      player: null
    }
  }

  monitor(media) {
    this.props.parentMonitor(media.getCurrentTime())
    setTimeout(this.playing.bind(this), 200)
  }

  playing() {
    this.monitor(this.state.player)
  }

  success(media, node, instance) {
    // successfully loaded!
    const playEvent = e => this.playing()
    media.addEventListener('playing', playEvent)
    media.removeEventListener('pause', playEvent)
    media.removeEventListener('ended', playEvent)
  }

  error(media) {
    // failed to load
  }

  componentDidMount() {
    console.log('Player mounted')
    const {MediaElementPlayer} = global
    if (MediaElementPlayer) {
      const options = {
        features: ['skipback'],
        useDefaultControls: true,
        pluginPath: './build/static/media/',
        skipBackInterval: 31,
        skipBackText: 'Rewind 30 seconds',
        success: (media, node, instance) => this.success(media, node, instance),
        error: (media, node) => this.error(media, node)
      }
      this.setState({player: new MediaElementPlayer('player_'+this.props.key, options)})
    }
  }

  componentWillUnmount() {
    console.log('Player will unmount')
    if (this.state.player) {
      this.state.player.remove()
      this.setState({player: null})
    }
  }

  shouldComponentUpdate() {
    return false
  }

  render() {
    console.log('render player')
    return (
      <Flex justify={this.state.children ? 'space-between' : ''} align="center">
        <Flex align="center">
          <audio id={'player_'+this.props.key} width={this.props.width || 400}>
            <source src={this.props.url} type={this.props.type} />
          </audio>
        </Flex>
      </Flex>
    )
  }
}

export default Player

细分:

import React from 'react'

import Box from '../../layout/Box'

import {lensPath, set, view} from 'ramda'

class Segments extends React.Component {
  constructor(props) {
    super()

    this.state = {
      segments: props.segments
    }
  }

  updateSegments(time) {
    console.log('time:', time)
    const firstPlayingLens = lensPath([0, 'playing'])
    if (time > 36 && !view(firstPlayingLens, this.state.segments)) {
      const modifiedSegments = set(firstPlayingLens, true, this.state.segments)
      console.log('modifiedSegments:', modifiedSegments)
      this.setState({segments: modifiedSegments})
    }
  }

  render() {
    console.log('render Segments')
    return (
      <Box mt={0} pt={0} bt>
        {this.state.segments.map(s => s.playing ? <p><strong>{s.title}</strong></p> : <p>{s.title}</p>)}
      </Box>
    )
  }
}

export default Segments

【讨论】:

    猜你喜欢
    • 2021-10-08
    • 2023-01-14
    • 1970-01-01
    • 1970-01-01
    • 2018-12-04
    • 1970-01-01
    • 2017-09-14
    • 2020-01-01
    • 2020-10-06
    相关资源
    最近更新 更多