Im React-Redux-Projekt erstellen die Benutzer normalerweise mehrere Aktionen und Reduzierungen für jede verbundene Komponente. Dadurch wird jedoch sehr viel Code für einfache Datenaktualisierungen erstellt.
Ist es eine gute Praxis, eine einzige generische Aktion & Reduzierer zu verwenden, um alle Datenänderungen zu kapseln, um die App-Entwicklung zu vereinfachen und zu beschleunigen .
Was wären die Nachteile oder der Leistungsverlust bei dieser Methode. Weil ich keinen signifikanten Kompromiss sehe und es die Entwicklung wesentlich vereinfacht, und wir alle diese Dateien in eine einzige Datei speichern können! Beispiel einer solchen Architektur:
// Say we're in user.js, User page
// state
var initialState = {};
// generic action --> we only need to write ONE DISPATCHER
function setState(obj){
Store.dispatch({ type: 'SET_USER', data: obj });
}
// generic reducer --> we only need to write ONE ACTION REDUCER
function userReducer = function(state = initialState, action){
switch (action.type) {
case 'SET_USER': return { ...state, ...action.data };
default: return state;
}
};
// define component
var User = React.createClass({
render: function(){
// Here's the magic...
// We can just call the generic setState() to update any data.
// No need to create separate dispatchers and reducers,
// thus greatly simplifying and fasten app development.
return [
<div onClick={() => setState({ someField: 1 })}/>,
<div onClick={() => setState({ someOtherField: 2, randomField: 3 })}/>,
<div onClick={() => setState({ orJustAnything: [1,2,3] })}/>
]
}
});
// register component for data update
function mapStateToProps(state){
return { ...state.user };
}
export default connect(mapStateToProps)(User);
So schlägt die typische Redux-Architektur Folgendes vor:
Die Frage ist, warum ein 2-Schritt-Prozess ? Hier ist ein weiterer architektonischer Vorschlag:
Erstellen Sie 1 Dateisatz , der alle setXField()
enthält, die alle Datenänderungen behandeln. Und andere Komponenten verwenden sie einfach, um Änderungen auszulösen. Einfach. Beispiel:
/** UserAPI.js
* Containing all methods for User.
* Other components can just call them.
*/
// state
var initialState = {};
// generic action
function setState(obj){
Store.dispatch({ type: 'SET_USER', data: obj });
}
// generic reducer
function userReducer = function(state = initialState, action){
switch (action.type) {
case 'SET_USER': return { ...state, ...action.data };
default: return state;
}
};
// API that we export
let UserAPI = {};
// set user name
UserAPI.setName = function(name){
$.post('/user/name', { name }, function({ ajaxSuccess }){
if (ajaxSuccess) setState({ name });
});
};
// set user picture URL
UserAPI.setPicture = function(url){
$.post('/user/picture', { url }, function({ ajaxSuccess }){
if (ajaxSuccess) setState({ url });
});
};
// logout, clear user
UserAPI.logout = function(){
$.post('/logout', {}, function(){
setState(initialState);
});
};
// Etc, you got the idea...
// Moreover, you can add a bunch of other User related methods,
// like some helper methods unrelated to Redux, or Ajax getters.
// Now you have everything related to User available in a single file!
// It becomes much easier to read through and understand.
// Finally, you can export a single UserAPI object, so other
// components only need to import it once.
export default UserAPI
Bitte lesen Sie die Kommentare im obigen Codeabschnitt.
Jetzt, anstatt eine Reihe von Aktionen/Dispatchern/Reduktionen zu haben. Sie haben 1 Datei, die alles enthält, was für das Benutzerkonzept benötigt wird . Warum ist es eine schlechte Praxis? IMO, macht das Leben des Programmierers viel einfacher , und andere Programmierer können die Datei einfach von oben nach unten durchlesen, um die Geschäftslogik zu verstehen, sie müssen nicht zwischen Aktionen/Reduzierungen hin und her wechseln Dateien. Heck, sogar redux-thunk
wird nicht benötigt! Und Sie können die Funktionen auch einzeln testen. So Testbarkeit geht nicht verloren.
Anstatt store.dispatch
in Ihrem Aktionsersteller aufzurufen, sollte stattdessen ein Objekt (action) zurückgegeben werden, was das Testen vereinfacht und das Rendern von Servern ermöglicht.
const setState = (obj) => ({
type: 'SET_USER',
data: obj
})
onClick={() => this.props.setState(...)}
// bind the action creator to the dispatcher
connect(mapStateToProps, { setState })(User)
Sie sollten auch ES6 class anstelle von React.createClass
verwenden.
Zurück zum Thema wäre ein spezialisierter Aktionsersteller so etwas wie:
const setSomeField = value => ({
type: 'SET_SOME_FIELD',
value,
});
...
case 'SET_SOME_FIELD':
return { ...state, someField: action.value };
Wenn someField
an mehreren Stellen festgelegt ist, ist es einfacher, setSomeField(someValue)
als setState({ someField: someValue })}
aufzurufen.
Sie können leicht setSomeField
testen, um sicherzustellen, dass nur der zugehörige Status korrekt geändert wird.
Mit dem generischen setState
können Sie auch auf setState({ someField: someValue })}
testen, aber es gibt keine direkte Garantie dafür, dass der gesamte Code dies korrekt aufruft.
Z.B. Jemand in Ihrem Team könnte einen Tippfehler machen und stattdessen setState({ someFeild: someValue })}
anrufen.
Die Nachteile sind nicht gerade signifikant, daher ist es in Ordnung, den generischen Aktionsersteller zu verwenden, um die Anzahl der spezialisierten Aktionsersteller zu reduzieren, wenn Sie der Meinung sind, dass sich das für Ihr Projekt lohnt.
In Bezug auf Ihren Vorschlag, Reduzierungen und Aktionen in derselben Datei abzulegen: Im Allgemeinen sollten diese für Modularität in separaten Dateien aufbewahrt werden. Dies ist ein allgemeiner Grundsatz, der nicht nur für React gilt.
Sie können jedoch verwandte Reduzier- und Aktionsdateien im selben Ordner ablegen, was je nach Projektanforderungen besser oder schlechter sein kann. Siehe das und das für einen Hintergrund.
Sie müssen auch userReducer
für Ihr Root-Reduktionsprogramm exportieren, es sei denn, Sie verwenden mehrere Stores, was im Allgemeinen nicht empfohlen ist.
Ich habe angefangen, ein package zu schreiben, um es einfacher und generischer zu gestalten. Auch zur Verbesserung der Leistung. Es befindet sich noch in einem frühen Stadium (38% Abdeckung). Hier ist ein kleiner Ausschnitt (wenn Sie neue ES6-Funktionen nutzen können), es gibt jedoch auch Alternativen.
import { create_store } from 'redux';
import { create_reducer, redup } from 'redux-decorator';
class State {
@redup("Todos", "AddTodo", [])
addTodo(state, action) {
return [...state, { id: 2 }];
}
@redup("Todos", "RemoveTodo", [])
removeTodo(state, action) {
console.log("running remove todo");
const copy = [...state];
copy.splice(action.index, 1);
return copy;
}
}
const store = createStore(create_reducer(new State()));
Sie können sogar Ihren Staat verschachteln:
class Note{
@redup("Notes","AddNote",[])
addNote(state,action){
//Code to add a note
}
}
class State{
aConstant = 1
@redup("Todos","AddTodo",[])
addTodo(state,action){
//Code to add a todo
}
note = new Note();
}
// create store...
//Adds a note
store.dispatch({
type:'AddNote'
})
//Log notes
console.log(store.getState().note.Notes)
Auf NPM sind viele Dokumentationen verfügbar. Fühlen Sie sich wie immer gerne dabei!
Ich verwende meistens Redux, um API-Antworten meistens zwischenzuspeichern. Hier sind einige Fälle, in denen ich dachte, es sei begrenzt.
1) Was ist, wenn ich verschiedene APIs anrufe, die den gleichen KEY haben, aber zu einem anderen Objekt gehen?
2) Wie kann ich darauf achten, dass es sich bei den Daten um einen Stream von einem Socket handelt? Muss ich das Objekt iterieren, um den Typ zu erhalten (da der Typ im Header und in der Antwort in der Payload enthalten sein wird) oder bitten Sie meine Back-End-Ressource, diesen mit einem bestimmten Schema zu senden.
3) Dies schlägt auch für api fehl, wenn wir einen Drittanbieter verwenden, bei dem wir keine Kontrolle über die Ausgabe haben, die wir erhalten.
Es ist immer gut, die Kontrolle darüber zu haben, welche Daten wohin gehen. In Apps, die sehr groß sind, etwa in einer Netzwerküberwachungsanwendung , werden die Daten möglicherweise überschrieben, wenn derselbe TASTE verwendet wird und durch das Eintippen von JavaScript dies beendet wird auf viel seltsame Weise funktioniert dies nur für wenige Fälle, in denen wir die vollständige Kontrolle über die Daten haben, was sehr wenige dieser Anwendung ähneln.
Okay, ich werde nur meine eigene Antwort schreiben:
wenn Sie Redux verwenden, stellen Sie sich diese beiden Fragen:
Befinden sich diese Komponenten in einem anderen Knotenbaum? Ich meine, es ist keine untergeordnete Komponente.
Lautet Ihre Antwort "Ja", verwenden Sie "Redux" für diese Daten, da Sie diese Daten einfach über die API "connect()
" an Ihre Komponenten übergeben können, wodurch sie in "containers
" umgewandelt werden.
Wenn Sie manchmal feststellen, dass Sie Daten an eine übergeordnete Komponente übergeben müssen, müssen Sie überdenken, wo Ihr Zustand lebt. Es gibt eine Sache namens Lifting the State Up.
Wenn Ihre Daten nur für Ihre Komponente von Bedeutung sind, sollten Sie setState
nur verwenden, um Ihren Bereich eng zu halten. Beispiel:
class MyComponent extends Component {
constructor() {
super()
this.state={ name: 'anonymous' }
}
render() {
const { name } = this.state
return (<div>
My name is { name }.
<button onClick={()=>this.setState({ name: 'John Doe' })}>show name</button>
</div>)
}
}
<ChildComponent yourdata={yourdata} />
In übergeordneter Komponente
updateName(name) {
this.setState({ name })
}
render() {
return(<div><ChildComponent onChange={::this.updateName} /></div>)
}
In untergeordneter Komponente
<button onClick={()=>this.props.onChange('John Doe')}
Hier ist ein guter Artikel darüber.
Beim Entwerfen von React/Redux-Programmen ist eine wichtige Entscheidung zu treffen, wo die Geschäftslogik abgelegt werden muss (sie muss irgendwo hingehen!).
Es könnte in den React-Komponenten, in den Aktionserstellern, in den Reduktionen oder in einer Kombination davon gehen. Ob die generische Kombination Aktion/Reduzierer sinnvoll ist, hängt davon ab, wo sich die Geschäftslogik bewegt.
Wenn die React-Komponenten den Großteil der Geschäftslogik beherrschen, können die Aktionsersteller und -reduzierer sehr leichtgewichtig sein und können, wie von Ihnen vorgeschlagen, problemlos in einer einzigen Datei abgelegt werden, es sei denn, die React-Komponenten werden komplexer.
Der Grund dafür, dass die meisten React/Redux-Projekte scheinbar viele Dateien für Aktionsersteller und -reduzierer enthalten, da einige der Geschäftslogiken dort eingefügt werden und daher zu einer sehr aufgeblähten Datei führen würden, wenn die generische Methode verwendet würde.
Ich persönlich bevorzuge sehr einfache Reduktionen und einfache Komponenten und habe eine große Anzahl von Aktionen, um die Komplexität wie das Anfordern von Daten von einem Webservice an die Ersteller der Action abzuräumen, aber der "richtige" Weg hängt von dem vorliegenden Projekt ab.
Ein kurzer Hinweis: Wie in https://stackoverflow.com/a/50646935 erwähnt, sollte das Objekt von setState
zurückgegeben werden. Dies liegt daran, dass möglicherweise einige asynchrone Verarbeitung ausgeführt werden muss, bevor store.dispatch
aufgerufen wird.
Ein Beispiel für die Reduzierung der Heizplatte ist unten. Hier wird ein generischer Reducer verwendet, der den erforderlichen Code reduziert, es ist jedoch nur möglich, dass die Logik an anderer Stelle gehandhabt wird, so dass Aktionen so einfach wie möglich gemacht werden.
import ActionType from "../actionsEnum.jsx";
const reducer = (state = {
// Initial state ...
}, action) => {
var actionsAllowed = Object.keys(ActionType).map(key => {
return ActionType[key];
});
if (actionsAllowed.includes(action.type) && action.type !== ActionType.NOP) {
return makeNewState(state, action.state);
} else {
return state;
}
}
const makeNewState = (oldState, partialState) => {
var newState = Object.assign({}, oldState);
const values = Object.values(partialState);
Object.keys(partialState).forEach((key, ind) => {
newState[key] = values[ind];
});
return newState;
};
export default reducer;
tldr Dies ist eine Entwurfsentscheidung, die zu einem frühen Zeitpunkt in der Entwicklung getroffen werden muss, da sie die Struktur eines großen Teils des Programms beeinflusst.
Zunächst einige Terminologie :
const someAction = {type: 'SOME_ACTION', payload: [1, 2, 3]}
'SOME_ACTION'
Sowohl in den Aktionserstellern als auch in den Reduzierern zu vermeiden. Sie definieren einen Aktionstyp wie const SOME_ACTION = 'SOME_ACTION'
, Damit Sie ihn in den Aktionserstellern und in den Reduzierern import
können.Nun zur Frage.
Ich denke, dass ein generischer Action-Creator keine großartige Idee ist.
Ihre Anwendung muss möglicherweise die folgenden Aktionsersteller verwenden:
fetchData()
fetchUser(id)
fetchCity(lat, lon)
Die Logik des Umgangs mit einer anderen Anzahl von Argumenten in einem einzigen Aktionsersteller zu implementieren, klingt für mich nicht richtig.
Ich denke, es ist viel besser, viele kleine Funktionen zu haben, weil sie unterschiedliche Verantwortlichkeiten haben. Beispielsweise sollte fetchUser
nichts mit fetchCity
zu tun haben.
Zunächst erstelle ich ein Modul für alle meine Aktionstypen und Aktionsersteller. Wenn meine Anwendung wächst, kann ich die Aktionsersteller in verschiedene Module aufteilen (z. B. actions/user.js
, actions/cities.js
), Aber ich halte separate Module für Aktionstypen für etwas übertrieben.
Was die Reduzierstücke betrifft, denke ich, dass ein einzelnes Reduzierstück eine gangbare Option ist, wenn Sie nicht mit zu vielen Aktionen fertig werden müssen.
Ein Reduzierer empfängt alle Aktionen, die von den Aktionserstellern ausgelöst wurden. Durch Betrachten von action.type
Wird dann ein neuer Status des Speichers erstellt. Da Sie sich ohnehin um alle eingehenden Aktionen kümmern müssen, finde ich es schön, die gesamte Logik an einem Ort zu haben. Dies ist natürlich schwierig, wenn Ihre Anwendung wächst (z. B. ist ein switch/case
Zur Verarbeitung von 20 verschiedenen Aktionen nicht sehr wartbar).
Sie können mit einem einzelnen Reduzierer beginnen, die Bewegung zu mehreren Reduzierern und diese in einem Wurzelreduzierer mit der Funktion combineReducer
kombinieren.
Leistung weise nicht viel. Aus gestalterischer Sicht jedoch einige. Durch mehrere Reduzierungen können Sie die Bedenken voneinander trennen - jedes Modul ist nur mit sich selbst beschäftigt. Durch das Erstellen von Aktionserstellern fügen Sie eine Ebene der Indirektion hinzu, mit der Sie Änderungen leichter vornehmen können. Am Ende kommt es immer noch darauf an, wenn Sie diese Funktionen nicht benötigen, hilft eine generische Lösung, den Code zu reduzieren.