更新 React 组件共享状态
Update React components sharing state
我有两个相互关联的项目列表。 B 列表有复选框。当我取消选中 B 列表的一个元素时,A 列表的相应元素应该被隐藏,但是 不幸的是渲染函数没有被调用。我使用减速器更改应用程序的状态。
我的问题是在更改状态后未调用渲染。由于点击事件由 B 列表处理,我可以 forceUpdate
该列表,但 A 列表将保持不变。
我该如何处理这个连接?
可选Links部分(列表)
import React, { PropTypes, Component } from 'react'
import Link from '../components/Link'
import LinkSelector from '../components/LinkSelector'
import {Popover, OverlayTrigger} from "react-bootstrap"
class SelectableLinksSection extends Component {
renderPopup() {
return (
<Popover id="idea-popup" className="imageP">
<LinkSelector links={this.props.links} showLink={this.props.showLink} hideLink={this.props.hideLink}/>
</Popover>
)
}
render() {
if (this.props.links.length) {
return (
<div>
<div className="col-sm-7">
<div className={this.props.cssClass}>
{
this.props.links.map(link => {
return <Link url={link.url} label={link.label} icon={link.icon} hide={link.hide}
text={link.text} widthClass="col-sm-4" dividerClass="divider"/>
})
}
</div>
</div>
<section className="col-sm-2 no-padding">
<div className="col-sm-12">
<div className="text-center add">
<OverlayTrigger trigger="click" rootClose placement="bottom"
overlay={this.renderPopup()}>
<a role="button" tabIndex="0" title="#i18n:dashboard.ideas.addLink#">
</a>
</OverlayTrigger>
</div>
</div>
</section>
</div>
)
} else {
return <div></div>
}
}
}
SelectableLinksSection.propTypes = {
cdn: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
text: PropTypes.string.isRequired,
links: PropTypes.array.isRequired,
cssClass: PropTypes.string.isRequired,
showLink: PropTypes.func.isRequired,
hideLink: PropTypes.func.isRequired
}
export default SelectableLinksSection;
Link选择器(B 列表)
import React, { PropTypes, Component } from 'react'
import Links from '../components/Links'
class LinkSelector extends Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this)
}
handleChange(link) {
if (link.hide) {
this.props.showLink(link.id)
} else {
this.props.hideLink(link.id)
}
this.forceUpdate()
}
render() {
return (
<div className="row">
{this.props.links.map((link)=> {
return (
<div key={"link-"+link.id} className="col-sm-3 text-center">
<div className="imgContainer"
style={{background:"url('"+link.icon+"') no-repeat center"}}>
<input type="checkbox" checked={!link.hide}
onChange={() => {this.handleChange(link)}}/>
</div>
<div className="title">{link.label}</div>
</div>
)
})}
</div>
)
}
}
LinkSelector.propTypes = {
links: PropTypes.array.isRequired,
showLink: PropTypes.func.isRequired,
hideLink: PropTypes.func.isRequired
}
export default LinkSelector;
Link(列表项)
import React, { PropTypes, Component } from 'react'
class Link extends Component {
render() {
return (
<div className={this.props.widthClass} style={this.props.hide ? {display: "none"} : {}}>
<div className="text-center">
<a href={this.props.url}><img alt={this.props.label} src={this.props.icon} /></a>
</div>
<div className={this.props.dividerClass}></div>
<div className="title"><a href={this.props.url}>{this.props.label}</a></div>
<div className="text" dangerouslySetInnerHTML={{__html: this.props.text}}></div>
</div>
)
}
}
Link.propTypes = {
label: PropTypes.string.isRequired,
icon: PropTypes.string.isRequired,
url: PropTypes.string.isRequired,
text: PropTypes.string.isRequired,
hide: PropTypes.bool.isRequired,
widthClass: PropTypes.string.isRequired,
dividerClass: PropTypes.string.isRequired
}
export default Link;
[编辑] SectionIdeas(列表容器)
import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux';
import SelectableLinksSection from '../components/SelectableLinksSection'
class SectionIdeas extends Component {
render() {
return <SelectableLinksSection title={this.props.title} text={this.props.text} links={this.props.links}
cdn={this.props.cdn} cssClass="row virtual list" showLink={this.props.show} hideLink={this.props.hide} />
}
}
SectionIdeas.propTypes = {
cdn: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
text: PropTypes.string.isRequired,
links: PropTypes.array.isRequired,
show: PropTypes.func.isRequired,
hide: PropTypes.func.isRequired
};
function mapStateToProps(state) {
return {
cdn: state.data.cdn,
title: state.data.ideas.title,
text: state.data.ideas.text,
links: state.data.ideas.links
};
}
export default connect(mapStateToProps)(SectionIdeas)
[编辑] 主容器
import React, { Component, PropTypes } from 'react'
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import SectionIdeas from './SectionIdeas'
import { readStateFromServer, setStateToServer } from '../actions/api';
import * as ideasActions from '../actions/ideas';
class App extends Component {
constructor(props) {
super(props);
props.dispatch(readStateFromServer());
}
render() {
const {dispatch} = this.props
const ideasBoundActions = bindActionCreators(ideasActions, dispatch)
dispatch(setStateToServer())
if (this.props.loaded) {
return (
<div>
<SectionIdeas {...ideasBoundActions} />
</div>
)
} else {
return (
<div className="loading">
#i18n:Loading#
</div>
)
}
}
}
App.propTypes = {
loaded: PropTypes.bool.isRequired,
dispatch: PropTypes.func.isRequired
};
function mapStateToProps(state) {
return {
loaded: state.data.loaded
};
}
export default connect(mapStateToProps)(App);
[编辑] 动作
export const SHOW = 'SHOW_LINKS'
export const HIDE = 'HIDE_LINKS'
export function show(id) {
return {
type: SHOW,
id: id
}
}
export function hide(id) {
return {
type: HIDE,
id: id
}
}
减速器
import { SHOW, HIDE } from '../actions/ideas'
import { RECEIVE_STATE } from '../actions/api'
export default function ideas(state = {}, action = "") {
switch (action.type) {
case RECEIVE_STATE:
return action.json && action.json.data && action.json.data.ideas ? action.json.data.ideas : state
case SHOW:
return toggle(state, action.id, true)
case HIDE:
return toggle(state, action.id, false)
default:
return state
}
}
function toggle(state, id, show) {
let newState = Object.assign({}, state)
newState.links.forEach((link)=> {
if (link.id === id) {
link.hide = !show
}
})
return newState
}
状态样本
{
"title":"Idee per insegnare in digitale",
"text":"Da qui puoi consultare siti di approfondimento",
"links":[
{
"id":1,
"icon":"http:\/\/cdn-my.zanichelli.local\/fe\/images\/dashboard\/spazio-clil.png",
"url":"http:\/\/online.scuola.zanichelli.it\/spazioclil\/",
"label":"Spazio CLIL",
"text":"",
"hide":false
},
{
"id":2,
"icon":"http:\/\/cdn-my.zanichelli.local\/fe\/images\/dashboard\/invalsi-myzanichelli.png",
"url":"http:\/\/online.scuola.zanichelli.it\/quartaprova\/",
"label":"Invalsi",
"text":"",
"hide":false
},
{
"id":3,
"icon":"http:\/\/cdn-my.zanichelli.local\/fe\/images\/dashboard\/aula-scienze-myzanichelli.png",
"url":"http:\/\/aulascienze.scuola.zanichelli.it\/",
"label":"Aula di Scienze",
"text":"",
"hide":false
},
{
"id":4,
"icon":"http:\/\/cdn-my.zanichelli.local\/fe\/images\/dashboard\/guida-myzanichelli.png",
"url":"http:\/\/altro.scuola.zanichelli.it",
"label":"Altro",
"text":"",
"hide":true
}
]
}
一种可能的解决方案(希望不是最好的)是将 handleChange 函数从 LinkSelector
组件(B 列表)移动到其父组件 SelectableLinksSection
(也包含 A 列表)并通过作为 属性 到 LinkSelector
的函数。这样 forceUpdate
函数强制两个列表的渲染方法
我有两个相互关联的项目列表。 B 列表有复选框。当我取消选中 B 列表的一个元素时,A 列表的相应元素应该被隐藏,但是 不幸的是渲染函数没有被调用。我使用减速器更改应用程序的状态。
我的问题是在更改状态后未调用渲染。由于点击事件由 B 列表处理,我可以 forceUpdate
该列表,但 A 列表将保持不变。
我该如何处理这个连接?
可选Links部分(列表)
import React, { PropTypes, Component } from 'react'
import Link from '../components/Link'
import LinkSelector from '../components/LinkSelector'
import {Popover, OverlayTrigger} from "react-bootstrap"
class SelectableLinksSection extends Component {
renderPopup() {
return (
<Popover id="idea-popup" className="imageP">
<LinkSelector links={this.props.links} showLink={this.props.showLink} hideLink={this.props.hideLink}/>
</Popover>
)
}
render() {
if (this.props.links.length) {
return (
<div>
<div className="col-sm-7">
<div className={this.props.cssClass}>
{
this.props.links.map(link => {
return <Link url={link.url} label={link.label} icon={link.icon} hide={link.hide}
text={link.text} widthClass="col-sm-4" dividerClass="divider"/>
})
}
</div>
</div>
<section className="col-sm-2 no-padding">
<div className="col-sm-12">
<div className="text-center add">
<OverlayTrigger trigger="click" rootClose placement="bottom"
overlay={this.renderPopup()}>
<a role="button" tabIndex="0" title="#i18n:dashboard.ideas.addLink#">
</a>
</OverlayTrigger>
</div>
</div>
</section>
</div>
)
} else {
return <div></div>
}
}
}
SelectableLinksSection.propTypes = {
cdn: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
text: PropTypes.string.isRequired,
links: PropTypes.array.isRequired,
cssClass: PropTypes.string.isRequired,
showLink: PropTypes.func.isRequired,
hideLink: PropTypes.func.isRequired
}
export default SelectableLinksSection;
Link选择器(B 列表)
import React, { PropTypes, Component } from 'react'
import Links from '../components/Links'
class LinkSelector extends Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this)
}
handleChange(link) {
if (link.hide) {
this.props.showLink(link.id)
} else {
this.props.hideLink(link.id)
}
this.forceUpdate()
}
render() {
return (
<div className="row">
{this.props.links.map((link)=> {
return (
<div key={"link-"+link.id} className="col-sm-3 text-center">
<div className="imgContainer"
style={{background:"url('"+link.icon+"') no-repeat center"}}>
<input type="checkbox" checked={!link.hide}
onChange={() => {this.handleChange(link)}}/>
</div>
<div className="title">{link.label}</div>
</div>
)
})}
</div>
)
}
}
LinkSelector.propTypes = {
links: PropTypes.array.isRequired,
showLink: PropTypes.func.isRequired,
hideLink: PropTypes.func.isRequired
}
export default LinkSelector;
Link(列表项)
import React, { PropTypes, Component } from 'react'
class Link extends Component {
render() {
return (
<div className={this.props.widthClass} style={this.props.hide ? {display: "none"} : {}}>
<div className="text-center">
<a href={this.props.url}><img alt={this.props.label} src={this.props.icon} /></a>
</div>
<div className={this.props.dividerClass}></div>
<div className="title"><a href={this.props.url}>{this.props.label}</a></div>
<div className="text" dangerouslySetInnerHTML={{__html: this.props.text}}></div>
</div>
)
}
}
Link.propTypes = {
label: PropTypes.string.isRequired,
icon: PropTypes.string.isRequired,
url: PropTypes.string.isRequired,
text: PropTypes.string.isRequired,
hide: PropTypes.bool.isRequired,
widthClass: PropTypes.string.isRequired,
dividerClass: PropTypes.string.isRequired
}
export default Link;
[编辑] SectionIdeas(列表容器)
import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux';
import SelectableLinksSection from '../components/SelectableLinksSection'
class SectionIdeas extends Component {
render() {
return <SelectableLinksSection title={this.props.title} text={this.props.text} links={this.props.links}
cdn={this.props.cdn} cssClass="row virtual list" showLink={this.props.show} hideLink={this.props.hide} />
}
}
SectionIdeas.propTypes = {
cdn: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
text: PropTypes.string.isRequired,
links: PropTypes.array.isRequired,
show: PropTypes.func.isRequired,
hide: PropTypes.func.isRequired
};
function mapStateToProps(state) {
return {
cdn: state.data.cdn,
title: state.data.ideas.title,
text: state.data.ideas.text,
links: state.data.ideas.links
};
}
export default connect(mapStateToProps)(SectionIdeas)
[编辑] 主容器
import React, { Component, PropTypes } from 'react'
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import SectionIdeas from './SectionIdeas'
import { readStateFromServer, setStateToServer } from '../actions/api';
import * as ideasActions from '../actions/ideas';
class App extends Component {
constructor(props) {
super(props);
props.dispatch(readStateFromServer());
}
render() {
const {dispatch} = this.props
const ideasBoundActions = bindActionCreators(ideasActions, dispatch)
dispatch(setStateToServer())
if (this.props.loaded) {
return (
<div>
<SectionIdeas {...ideasBoundActions} />
</div>
)
} else {
return (
<div className="loading">
#i18n:Loading#
</div>
)
}
}
}
App.propTypes = {
loaded: PropTypes.bool.isRequired,
dispatch: PropTypes.func.isRequired
};
function mapStateToProps(state) {
return {
loaded: state.data.loaded
};
}
export default connect(mapStateToProps)(App);
[编辑] 动作
export const SHOW = 'SHOW_LINKS'
export const HIDE = 'HIDE_LINKS'
export function show(id) {
return {
type: SHOW,
id: id
}
}
export function hide(id) {
return {
type: HIDE,
id: id
}
}
减速器
import { SHOW, HIDE } from '../actions/ideas'
import { RECEIVE_STATE } from '../actions/api'
export default function ideas(state = {}, action = "") {
switch (action.type) {
case RECEIVE_STATE:
return action.json && action.json.data && action.json.data.ideas ? action.json.data.ideas : state
case SHOW:
return toggle(state, action.id, true)
case HIDE:
return toggle(state, action.id, false)
default:
return state
}
}
function toggle(state, id, show) {
let newState = Object.assign({}, state)
newState.links.forEach((link)=> {
if (link.id === id) {
link.hide = !show
}
})
return newState
}
状态样本
{
"title":"Idee per insegnare in digitale",
"text":"Da qui puoi consultare siti di approfondimento",
"links":[
{
"id":1,
"icon":"http:\/\/cdn-my.zanichelli.local\/fe\/images\/dashboard\/spazio-clil.png",
"url":"http:\/\/online.scuola.zanichelli.it\/spazioclil\/",
"label":"Spazio CLIL",
"text":"",
"hide":false
},
{
"id":2,
"icon":"http:\/\/cdn-my.zanichelli.local\/fe\/images\/dashboard\/invalsi-myzanichelli.png",
"url":"http:\/\/online.scuola.zanichelli.it\/quartaprova\/",
"label":"Invalsi",
"text":"",
"hide":false
},
{
"id":3,
"icon":"http:\/\/cdn-my.zanichelli.local\/fe\/images\/dashboard\/aula-scienze-myzanichelli.png",
"url":"http:\/\/aulascienze.scuola.zanichelli.it\/",
"label":"Aula di Scienze",
"text":"",
"hide":false
},
{
"id":4,
"icon":"http:\/\/cdn-my.zanichelli.local\/fe\/images\/dashboard\/guida-myzanichelli.png",
"url":"http:\/\/altro.scuola.zanichelli.it",
"label":"Altro",
"text":"",
"hide":true
}
]
}
一种可能的解决方案(希望不是最好的)是将 handleChange 函数从 LinkSelector
组件(B 列表)移动到其父组件 SelectableLinksSection
(也包含 A 列表)并通过作为 属性 到 LinkSelector
的函数。这样 forceUpdate
函数强制两个列表的渲染方法