我已经从使用纯jsons转变为实例。 意思是,我将实例存储在组件状态中。 问题是实例无法更新自己的属性,因为这意味着要改变状态,所以我需要克隆实例,从实例外部更新,然后设置状态。
我想将实例存储在组件上并存储一个虚拟状态属性,一旦实例属性发生更改,它就会更新将触发重新渲染的虚拟状态。
由于实例与UI有关,我知道它们应该位于该状态。
我知道这不是最佳做法,但我试图弄清楚它有多糟糕。
例如:
class Team {
constructor(data) {
this._teamMates = null;
this.id = data.id;
}
get teamMates() {
if (!this._teamMates) {
this.fetchTeamMates();
return null;
}
else
return this._teamMates;
}
fetchTeamMates() {
fetch('/teamMates/' + this.id).then(teamMates => this._teamMates = teamMates);
}
}
当我们第一次尝试获得teamMated时,它们无效,因此我们获取它们,并且在下一次状态更改时,我希望它们作为teamMates有效。
我可以返回一个promise而不是null,这是可能的,但我想以这种方式处理它,因为如果我们有teamMates,我想要条件渲染。
我知道有多种方法可以解决这个问题,但我宁愿尝试找到一种方法让它在不返回承诺的情况下发挥作用。
答案 0 :(得分:0)
我不完全确定你的意思:
"问题是实例无法更新自己的属性,因为这意味着要改变状态,所以我需要克隆实例,从实例外部更新,然后设置状态。&# 34;
听起来最简单的事情是在父HOC中创建状态和方法,将它们传递给子组件并在事件处理程序中触发操作,然后它应该更新父状态并导致正常渲染孩子理想情况下,您的孩子将成为一个愚蠢的组件",字面上只是传递值和动作以及渲染。
答案 1 :(得分:0)
我认为你正在寻找一些州长,例如redux(你没有从你的问题中排除它)
如果你真的想要保留自己的实例(我不认为这是必要的,因为它没有必要,你正在处理状态),那么你应该创建一个不可变版本的类,也就是说,如果您更新团队中的玩家,则应返回团队实例的新版本,并更改播放器数据。
以下示例不使用实例,但它将团队配合移动到商店,并让redux管理状态(因此将其从组件状态中删除)。
要关注的主要关键点是:
connect
语句,将组件与其props中注入的(部分)状态连接起来,并添加一个dispatcher
来处理您发送给它的操作,如下所示:
const ConnectedTeamEditor = connect( teamStateToProps, playerDispatcher )( TeamEditor );
这需要一个状态映射器和调度程序,看起来非常像这样:
// dispatcher for the actions
const playerDispatcher = dispatch => ({
fetch() {
return getTeamMates().then( response => dispatch( { type: 'loaded', payload: response } ) );
},
update( player ) {
dispatch( { type: 'update', payload: player } );
}
});
// state mapper, sharing teamMates state over the connected components props
const teamStateToProps = state => ({ teamMates: state.teamMates });
要处理调度调用,我们需要一个还原器,然后注册到商店,我们使用react-redux提供的createStore
方法。 reducer可以有一个默认状态
// reducer for player actions
const playerReducer = ( state = { teamMates: null }, action ) => {
switch ( action.type ) {
case 'loaded':
return { teamMates: action.payload };
case 'update':
return { teamMates: state.teamMates.map( player => player.id === action.payload.id ? action.payload : player ) };
default:
return state;
}
};
// and registration to store
const appStore = createStore( playerReducer );
React-redux会在状态更新后自动更新受影响的组件。需要注意的重要一点是,状态不应该发生变异,应该在需要时进行替换。如果找不到匹配的action.type
,则返回状态也很重要。
最后,应用程序应该包含一个Provider
,它通过其道具接收store
,然后看起来就像那样。
ReactDOM.render( <Provider store={appStore}><ConnectedTeamEditor /></Provider>, target );
我确实保留了组件状态,但仅用于更新当前选定的播放器。
由于我们大多数人都处于WK心情,我有点自由设计,只需运行代码看看我的意思^ _ ^
const { createStore } = Redux;
const { Provider, connect } = ReactRedux;
// data provider
function getTeamMates() {
return Promise.resolve([
{ id: 1, firstName: 'Romelu', lastName: 'Lukaku', position: 'forward' },
{ id: 2, firstName: 'Dries', lastName: 'Mertens', position: 'forward' },
{ id: 3, firstName: 'Eden', lastName: 'Hazard', position: 'forward' },
{ id: 4, firstName: 'Radja', lastName: 'Naingolan', position: 'midfield' },
{ id: 5, firstName: 'Kevin', lastName: 'De Bruyne', position: 'midfield' },
{ id: 6, firstName: 'Jordan', lastName: 'Lukaku', position: 'defender' },
{ id: 7, firstName: 'Axel', lastName: 'Witsel', position: 'midfield' },
{ id: 8, firstName: 'Vincent', lastName: 'Kompany', position: 'defender' },
{ id: 9, firstName: 'Thomas', lastName: 'Meunier', position: 'defender' },
{ id: 10, firstName: 'Marouane', lastName: 'Fellaini', position: 'midfield' },
{ id: 11, firstName: 'Thibaut', lastName: 'Courtois', position: 'Goalie' }
]);
}
// some container to translate the properties to readable names
const labels = {
'id': '#',
'firstName': 'First Name',
'lastName': 'Last Name',
'position': 'Position'
};
// small function to return the correct translation or default value
const translateProperty = ( property ) => labels[property] || property;
// a field that is either editable or just shows a span with a value
const Field = ({ isEditable, value, onChange }) => {
if ( isEditable ) {
return <span className="value">
<input type="text" value={ value } onChange={ e => onChange( e.target.value ) } />
</span>;
}
return <span className="value">{ value }</span>;
};
// a single player, delegating changes to its data and selection style
const Player = ({ player, isSelected, onChange, onSelect }) => {
return (
<div className={classNames('row', { isSelected })} onClick={ () => !isSelected && onSelect && onSelect( player ) }>
{ Object.keys( player ).map( property => (
<div className="cell" key={property}>
<span className="label">{ translateProperty( property ) }</span>
<Field
isEditable={isSelected && property !== 'id' }
value={ player[property] }
onChange={ newValue => onChange( {...player, [property]: newValue } ) }
/>
</div>
) ) }
</div>
);
};
// reducer for player actions
const playerReducer = ( state = { teamMates: null }, action ) => {
switch ( action.type ) {
case 'loaded':
return { teamMates: action.payload };
case 'update':
return { teamMates: state.teamMates.map( player => player.id === action.payload.id ? action.payload : player ) };
case 'add':
return { teamMates: state.teamMates.concat( [ {
id: state.teamMates.reduce( (c, i) => c > i.id ? c : i.id, 0 ) + 1,
...action.payload } ] ) };
default:
return state;
}
};
// dispatcher for the actions
const playerDispatcher = dispatch => ({
fetch() {
return getTeamMates().then( response => dispatch( { type: 'loaded', payload: response } ) );
},
update( player ) {
dispatch( { type: 'update', payload: player } );
},
add( player ) {
dispatch( { type: 'add', payload: player } );
}
});
const teamStateToProps = state => ({ teamMates: state.teamMates });
// the team editor that works with props
class TeamEditor extends React.Component {
constructor() {
super();
this.state = {
selectedPlayer: null,
newPlayer: null
};
}
componentWillMount() {
// load when mounting
this.props.fetch();
}
selectPlayer( player ) {
// component state keeps the selected player
this.setState( { selectedPlayer: player } );
}
updatePlayer( player ) {
// update player through dispatcher
this.props.update( player );
}
onAddClicked() {
this.setState( {
isAdding: true,
newPlayer: {
firstName: '',
lastName: '',
position: ''
}
} );
}
updateNewPlayer( player ) {
this.setState( { newPlayer: player } );
}
savePlayer() {
this.setState( { isAdding: false } , () => this.props.add( this.state.newPlayer ) );
}
cancelChanges() {
this.setState( { isAdding: false } );
}
render() {
const { teamMates} = this.props;
const { selectedPlayer, isAdding, newPlayer } = this.state;
return (
<div className="team">
<div className="row">
{ !isAdding && <button
type="button"
onClick={ () => this.onAddClicked() }>Add team member</button>
}
{ isAdding && <span>
<button
type="button"
onClick={ () => this.savePlayer() }>Save</button>
<button
type="button"
onClick={ () => this.cancelChanges() }>Cancel</button>
</span> }
</div>
{ isAdding && <div className="row">
<Player
isSelected
player={newPlayer}
onChange={ (...args) => this.updateNewPlayer( ...args ) } />
</div> }
{ teamMates && teamMates.map( player => (
<Player
key={player.id}
isSelected={ !isAdding && selectedPlayer && selectedPlayer.id === player.id }
player={player}
onChange={ (...args) => this.updatePlayer( ...args ) }
onSelect={ (...args) => this.selectPlayer( ...args ) }
/> ) ) }
</div>
);
}
}
// connect the teameditor with state and dispatcher
const ConnectedTeamEditor = connect( teamStateToProps, playerDispatcher )( TeamEditor );
// create a simple store, no middleware
const appStore = createStore( playerReducer );
const target = document.querySelector('#container');
ReactDOM.render( <Provider store={appStore}><ConnectedTeamEditor /></Provider>, target );
&#13;
* { box-sizing: border-box; }
body { margin: 0; padding: 0; }
.row {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-evenly;
align-content: center;
}
.row:hover {
cursor: pointer;
}
.row.isSelected > .cell > * {
background-image: linear-gradient( rgba( 225, 225, 225, 0.7 ), rgba( 255, 255, 255, 0.9 ) );
}
.cell {
display: flex;
flex-direction: column;
flex-basis: 25%;
flex-grow: 0;
flex-shrink: 0;
align-self: flex-start;
background-color: yellow;
}
.cell:first-child {
background-color: red;
}
.cell:last-child {
background-color: black;
color: #fff;
}
.cell > span {
padding: 5px;
}
.cell > span > input {
width: 100%;
}
.label {
text-transform: capitalize;
}
&#13;
<script id="react" src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.2/react.js"></script>
<script id="react-dom" src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/15.6.2/react-dom.js"></script>
<script id="classnames" src="https://cdnjs.cloudflare.com/ajax/libs/classnames/2.2.5/index.js"></script>
<script id="redux" src="https://cdnjs.cloudflare.com/ajax/libs/redux/3.7.2/redux.js"></script>
<script id="react-redux" src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/5.0.6/react-redux.js"></script>
<div id="container"></div>
&#13;