React Child 组件将在调用 parent 函数设置 parent 状态后挂载然后卸载
React Child component will mount then unmount after calling parent function to set parent state
我有两个组件,parent 和 child。 parent 跟踪音频播放器组件(child 是播放器)以及播放器正在播放的片段,例如第 1 段可能是前 34 秒,然后是第二段,直到 215 秒,依此类推。
我的 parent 组件呈现播放器组件并将绑定函数传递给播放器,这样播放器就可以用播放器的当前时间更新 parent 这样 parent 就可以找出应该突出显示的部分。
问题 是 (1)(主要问题)一旦播放按钮被点击并播放,或者用户跳过,超过第一个片段中断然后状态parent 更新但播放器被卸载,导致 MediaElement 被移除; (2) (小问题)当最初加载页面时,播放器卸载,然后是 parent 安装,然后播放器卸载并再次安装。我相信他们是相关的。
parent:
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。
在@Hoyen 的帮助下弄清楚重新渲染是由父级的状态更改引起的,我发现我需要将父级的 state
与段的 state
分开.我将这些片段放在它们自己的子 class 中,并在玩家时间更新时从父级调用它们。
注意(在父级中)对段子 this.refs.segments.updateSegments
的调用和段的父渲染中的 ref
属性,<Segments ref="segments" key={"bSegments"+this.state.briefing.id} segments={segments}></Segments>
这使得调用子组件。
家长:
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
我有两个组件,parent 和 child。 parent 跟踪音频播放器组件(child 是播放器)以及播放器正在播放的片段,例如第 1 段可能是前 34 秒,然后是第二段,直到 215 秒,依此类推。
我的 parent 组件呈现播放器组件并将绑定函数传递给播放器,这样播放器就可以用播放器的当前时间更新 parent 这样 parent 就可以找出应该突出显示的部分。
问题 是 (1)(主要问题)一旦播放按钮被点击并播放,或者用户跳过,超过第一个片段中断然后状态parent 更新但播放器被卸载,导致 MediaElement 被移除; (2) (小问题)当最初加载页面时,播放器卸载,然后是 parent 安装,然后播放器卸载并再次安装。我相信他们是相关的。
parent:
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。
在@Hoyen 的帮助下弄清楚重新渲染是由父级的状态更改引起的,我发现我需要将父级的 state
与段的 state
分开.我将这些片段放在它们自己的子 class 中,并在玩家时间更新时从父级调用它们。
注意(在父级中)对段子 this.refs.segments.updateSegments
的调用和段的父渲染中的 ref
属性,<Segments ref="segments" key={"bSegments"+this.state.briefing.id} segments={segments}></Segments>
这使得调用子组件。
家长:
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