<p>Présentation de dva</p>

Créé le 24 juin 2016  ·  76Commentaires  ·  Source: dvajs/dva

Il n'y a pas de nouveaux concepts, tous sont anciens.

Pourquoi dva ?

Après une période d'auto-apprentissage ou de formation, chacun devrait être capable de comprendre le concept de redux et de reconnaître que ce contrôle du flux de données peut rendre l'application plus contrôlable et la logique plus claire.

Mais alors, il y a généralement une telle question : il y a trop de concepts, et le réducteur, la saga et l'action sont tous séparés (sous-fichiers).

Le problème avec ceci est:

  • Le coût d'édition est élevé et vous devez basculer entre le réducteur, la saga et l'action
  • Il n'est pas pratique d'organiser le modèle d'entreprise (ou le modèle de domaine). Par exemple, après avoir écrit une liste d'utilisateurs, pour écrire une liste de produits, nous devons copier beaucoup de fichiers.

Et quelques autres :

  • L'écriture de saga est trop compliquée. Chaque fois que vous écoutez une action, vous devez passer par le processus de fork -> watcher -> worker
  • problème d'écriture d'entrée
  • ...

Et dva est utilisé pour résoudre ces problèmes.

C'est quoi dva ?

dva est un package léger basé sur l'architecture applicative existante (redux + react-router + redux-saga, etc.), sans introduire de nouveaux concepts, et le code total est inférieur à 100 lignes. (Inspiré par l'orme et le choo.)

dva est un framework, pas une bibliothèque. Semblable à emberjs, il vous indiquera clairement comment chaque composant doit être écrit, ce qui est plus contrôlable pour l'équipe. De plus, dva encapsule toutes les autres dépendances à l'exception de react et react-dom qui sont peerDependencies.

Dans l'implémentation de dva, essayez de ne pas créer une nouvelle syntaxe, mais utilisez la syntaxe de la bibliothèque de dépendances elle-même, telle que la définition du routeur ou la syntaxe JSX de react-router (la configuration dynamique est une considération de performance, qui sera prise en charge plus tard).

Le cœur de celui-ci est de fournir la méthode app.model , qui est utilisée pour encapsuler le réducteur, l'état initial, l'action et la saga ensemble, comme :

app.model({
  namespace: 'products',
  state: {
    list: [],
    loading: false,
  },
  subscriptions: [
    function(dispatch) {
      dispatch({type: 'products/query'});
    },
  ],
  effects: {
    ['products/query']: function*() {
      yield call(delay(800));
      yield put({
        type: 'products/query/success',
        payload: ['ant-tool', 'roof'],
      });
    },
  },
  reducers: {
    ['products/query'](state) {
      return { ...state, loading: true, };
    },
    ['products/query/success'](state, { payload }) {
      return { ...state, loading: false, list: payload };
    },
  },
});

Avant dva, nous créions normalement sagas/products.js , reducers/products.js et actions/products.js puis basculions entre ces fichiers.

Présentez les clés de ces modèles : (en supposant que vous êtes déjà familiarisé avec l'architecture d'application de redux, redux-saga)

  • espace de noms - correspond à la valeur clé du réducteur lors de la combinaison avec le rootReducer
  • state - correspond à l'état initial du réducteur
  • abonnement - un nouveau concept de [email protected] , exécuté après dom ready, aucune explication ici, voir : A Farewell to FRP
  • effets - correspond à saga et simplifie l'utilisation
  • réducteurs

Comment utiliser

Reportez-vous aux exemples :

Feuille de route

  • [x] devtool support d'échange à chaud
  • [x] Le routeur prend en charge la configuration dynamique
  • [x] Les effets doivent prendre en charge plus de modes saga
  • [ ] Effects envisage d'étendre l'accès aux solutions thunk, promises, observables et autres, l'objectif fondamental est d'être compatible avec IE8
  • [ ] Il est trop gênant de faire passer la répartition entre les composants, considérez le plan suivant
  • [x] Solution de test unitaire
  • [x] Plus d'exemples : todolist, utilisateurs dans antd-init, produits populaires

    FAQ

Support au niveau de l'outil de développement ?

Outre le remplacement à chaud, qui doit encore être adapté, d'autres tels que redux-devtool, css livereload, etc. sont tous compatibles.

Est-il déjà disponible pour l'environnement de construction ?

Pouvez.

Inclut-il toutes les fonctionnalités de l'architecture d'application précédente redux + redux-saga ?

Oui.

Compatibilité navigateur ?

IE8 ne le supporte pas car redux-saga est utilisé. (Nous envisagerons de prendre en charge les thunks, les promesses, les observables, etc. dans la couche d'effets de manière étendue plus tard)

Commentaire le plus utile

Être rendu vivant par redux est tout simplement l'évangile. C'est trop simple et élégant. Grand éloge ! ! !

au fait, j'ai accidentellement vu un étranger le republier sur Twitter aujourd'hui, je pensais qu'il avait été écrit par un étranger, mais je ne m'attendais pas à ce que ce soit un camarade de classe d'Alipay, 👍

Tous les 76 commentaires

Être rendu vivant par redux est tout simplement l'évangile. C'est trop simple et élégant. Grand éloge ! ! !

au fait, j'ai accidentellement vu un étranger le republier sur Twitter aujourd'hui, je pensais qu'il avait été écrit par un étranger, mais je ne m'attendais pas à ce que ce soit un camarade de classe d'Alipay, 👍

Dans l'attente de l'expansion des effets

L'environnement de production Alipay utilise-t-il cette architecture ?

@besteric dva vient de sortir et n'a pas encore été appliqué, mais l' architecture d'application sous-jacente est utilisée depuis un certain temps.

Le réducteur peut-il s'écrire ainsi :

const reducer = (state, { type, payload }) => {
  switch (type) {
    case 'products/query':
      return { ...state, loading: true, };
    case 'products/query/success':
      return { ...state, loading: false, list: payload };
    default
      return state;
  }
}

app.model({
  reducer
})

Cela permet d'appliquer certaines méthodes d'ordre supérieur au réducteur.

Louange, j'ai écrit quelques démos et il n'y a qu'un seul problème, le modèle ne peut être utilisé
app.model(Model1); app.model(Model2);
Est-ce que cette méthode complète la combinaison, en fait, je pense que l'idéal est
app.model([Model1,Model2])
un certain type de

Il est trop gênant de transmettre la répartition entre les composants, envisagez la solution suivante

Ne pas utiliser bindActionCreators ?

Le scénario spécifique de l'utilisation avancée du réducteur @yesmeck est -il uniquement redo/undo ? Je ne veux pas que dva soit trop flexible, et j'envisagerai de l'ajouter via un module complémentaire à l'avenir.

Nous en utilisons beaucoup dans notre projet. Par exemple, nous allons extraire les parties similaires de plusieurs réducteurs dans une méthode de haut niveau pour modifier le réducteur d'origine, et il existe des méthodes de haut niveau qui permettent au réducteur de réinitialiser l'état lorsque la route changements. , et ce https://github.com/erikras/multireducer

@ Tinker404 Je pense qu'il serait plus clair de déclarer le modèle séparément et qu'il serait plus facile d'ajouter et de supprimer. J'écrirais ceci :

app.model(require('../models/a'));
app.model(require('../models/b'));

@JimmyLv préfère personnellement ne pas utiliser actionCreator, mais juste dispatch .

@yesmeck ok, je vais y repenser.

Il existe également des méthodes d'ordre supérieur qui permettent au réducteur de réinitialiser l'état lorsque l'itinéraire change

Je pense que ce scénario est plus approprié en souscrivant aux changements de routage dans subscriptions , puis en réinitialisant l'état par l'action. Ou y a-t-il un avantage à utiliser la méthode du réducteur-amplificateur ?

Je pense que ce scénario est plus approprié en souscrivant aux changements de routage dans les abonnements, puis en réinitialisant l'état par l'action

Dans ce cas, chaque réducteur qui doit être réinitialisé doit écrire la logique de réinitialisation. Si nous utilisons une méthode de haut niveau, nous n'avons qu'à le faire maintenant :

combineReducers({
  products: composeReducers({  // composeReducers 的实现见下面
    recycle(LOCATION_CHANGE, initialState),  // recycle 用来在路由变化时重置状态
    products
  })
})

Un autre scénario est la même logique dont je parle pour extraire différents réducteurs. Par exemple, il y a une liste de produits et une liste d'utilisateurs, et leurs réducteurs sont comme ceci :

// reducers/products.js
const reducer = (state, { type, action}) => {
  switch (type) {
    case 'products/FETCH_SUCCESS':
      return {
        ...state,
        loading: false,
        list: payload
      }
    default:
      return state
  }
}
// reducers/users.js
const reducer = (state, { type, payload}) => {
  switch (type) {
    case 'users/FETCH_SUCCESS':
      return {
        ...state,
        loading: false,
        list: payload
      }
    default:
      return state
  }
}

Ici, les deux réducteurs sont presque identiques, donc nous les extrayons et écrivons un réducteur de liste :

const list = (actionType) => {
  return (state, { type, payload }) => {
    switch (type) {
      case actionType:
        return {
          ...state,
          loading: false,
          list: payload
        }
        break;
      default:
        return state
    }
  }
}

Ensuite, nous implémentons un composeReducers pour combiner ces 3 réducteurs :

function composeReducers(...reducers) {
  return (state, action) => {
    if (reducers.length === 0) {
      return state
    }

    const last = reducers[reducers.length - 1]
    const rest = reducers.slice(0, -1)

    return rest.reduceRight((enhanced, reducer) => reducer(enhanced, action), last(state, action))
  }
}

De cette manière, le réducteur pour la liste des produits et la liste des utilisateurs devient ceci :

// reducers/products.js
const reducer = (state, { type, payload}) => {
  // 其他逻辑
}

export default composeReducer(reducer, list('products/FETCH_SUCCESS'))
// reducers/users.js
const reducer = (state, { type, payload}) => {
  // 其他逻辑
}

export default composeReducer(reducer, list('users/FETCH_SUCCESS'))

list n'est qu'un exemple, en fait, il existe de nombreux réducteurs dans le projet qui ont la même logique.

@yesmeck 👍, le rôle de réducteur amplificateur a été sous-estimé auparavant.

@sorrycc pouvez-vous dire pourquoi ? Appelé explicitement avec une comparaison dispatch ?

@ Tinker404 Je pense qu'il serait plus clair de déclarer le modèle séparément et qu'il serait plus facile d'ajouter et de supprimer. J'écrirais ceci :
app.model(require('../models/a'));
app.model(require('../models/b'));

Je suggère également une méthode qui peut passer dans plusieurs modèles à la fois. Les grands projets peuvent avoir de nombreux modèles. J'ai maintenant besoin (importer) de tous, puis modéliser chaque modèle un par un, ce qui n'est pas très pratique. Ma méthode actuelle d'écriture est :

// models是个文件夹,有很多model
import models from './models';

models.forEach((m)=>{
    app.model(m);
});

// models.js
const context = require.context('./', false, /\.js$/);
const keys = context.keys().filter(item => item !== './index.js');
const models = [];
for (let i = 0; i < keys.length; i++) {
  models.push(context(keys[i]));
}
export default models;

C'est très D.VA.

J'ai trouvé l'utilisation du composant de formulaire antd dans le tableau de bord utilisateur.Je me souviens qu'il ne peut pas être utilisé pour un composant pur.Est-ce possible maintenant ?

@codering Je ne me souviens pas qu'il y ait des restrictions, les problèmes avec antd peuvent être posés sur https://github.com/ant-design/ant-design/issues .

Bonjour, je souhaite utiliser votre dva. Actuellement, j'utilise la structure de répertoires générée par l'échafaudage React Webpack Redux. J'ai modifié le code en référence à l'exemple de tableau de bord utilisateur dans votre exemple, mais il n'y a rien après le démarrage. Pouvez-vous aider moi savoir où il est? Quelque chose s'est mal passé, mon adresse de projet: https://github.com/baiyulong/lenovo_parts

@baiyulong pourquoi ne pas le faire directement en fonction de la structure de répertoires du tableau de bord utilisateur ?

@sorrycc J'utilise maintenant la structure de répertoires du tableau de bord utilisateur.Y a-t-il un traitement spécial ou une écriture pour le routage dva?
export default function({ history }) {
return (
<Router history={history}>
<IndexRoute component={HomePage} />
<Route path='/' component={HomePage}>
<Route path='/create' component={CreateOrder} />
</Route>
</Router>
)
}
Cette route que j'ai écrite, HomePage peut, j'ai écrit un lien <Link to='/create'>Create</Link> , je ne peux pas accéder au composant CreateOrder après avoir cliqué dessus

Il n'y a pas de manière spéciale d'écrire l'itinéraire de @baiyulong dva , veuillez essayer :

  1. Y a-t-il une erreur
  2. Essayez d'accéder directement à la route /create

@nikogu merci beaucoup, j'irai bien après avoir sorti le nid

Bonjour, dva peut-il prendre en charge le chargement à chaud de modèles ?

@kkkf1190 envisage cela et le soutiendra.

👍

Je voulais juste vous dire merci. . .

J'ai toujours pensé que l'échafaudage de vue-cli de vuejs était très bon. Après avoir lu ceci, ma pensée a complètement changé.

Cadre très magnifique ! Recherche depuis un moment. @sorrycc Je veux poser deux questions à Yunda :

  1. dva peut-il être parfaitement utilisé dans des projets natifs réactifs?
  2. dva+reactjs peut-il bien prendre en charge le rendu côté serveur ?

@freemember007

  1. Prise en charge de react-native, exemple de référence : https://github.com/sorrycc/dva-example-react-native
  2. Il n'y a pas de problème dans le fonctionnement du serveur en théorie. Redux et le routeur de réaction derrière prennent en charge SSR, mais il faudra un certain temps pour l'appliquer à dva, car la logique pertinente doit être redressée et bien emballée.

@sorrycc Existe-t-il maintenant une solution pour la prise en charge des réducteurs d'ordre supérieur? Notre projet utilise beaucoup de réducteurs d'ordre élevé en raison de la réutilisation

Pris en charge par @ancheel , peut être global ou local, cas d'utilisation de référence : https://github.com/dvajs/dva/blob/master/test/reducers-test.js

Une fois l'état du modèle modifié, comment le modifier à nouveau, ce problème se produit toujours maintenant
antd.js:32924 Avertissement : setState(...) : impossible de mettre à jour pendant une transition d'état existante (par exemple, dans render ou dans le constructeur d'un autre composant). Les méthodes de rendu doivent être une fonction pure des accessoires et de l'état ; constructeur les effets secondaires sont un anti-modèle, mais peuvent être déplacés vers componentWillMount .

Très excitant, essayez de l'utiliser dans l'environnement de production, espérons continuer à optimiser et à améliorer

nerf ça !

Bon travail 。Merci !!

@sorrycc Au plaisir de prendre en charge le rendu côté serveur !

Pris en charge par @mountainmoon , reportez-vous à https://github.com/sorrycc/dva-boilerplate-isomorphic .

Une vague de roues est arrivée :+1:

Bonjour, je viens de prendre contact avec l'apprentissage de ce dva. Après avoir lu le code pendant quelques jours, j'ai quelques questions dans mon cœur. Je voudrais demander :
J'ai vu que vos démos sont toutes des applications d'une seule page, mais ce sont toutes des applications multipages en développement. Je voudrais demander, si le routage n'est pas utilisé dans le développement d'applications multipages, comment charger des composants à la place, peut-être que je suis demander à un idiot. C'est un peu déroutant, car je n'utilise pas de routage, donc l'écouteur défini dans les modèles ne sait pas où déclencher :
histoire.écouter( emplacement => {
if(location.pathname === '/users') {
envoi({
tapez:'querySuccess',
charge utile:{}
})
}
})
PS : lors du chargement des données dans la méthode querySuccess et de l'utilisation de l'exportation par défaut connect(mapStateToProps)(Users) ; une erreur est également signalée :
connect.js:41 Uncaught TypeError : Impossible d'appeler une classe en tant que fonction
Je me sens comme un idiot en un instant, je ne sais pas si je peux te déranger pour me l'expliquer, merci !

Pourquoi dva ? en anglais s'il vous plaît

Je n'aime pas trop cette façon d'écrire.

@codering vous avez mentionné l'utilisation de composants de formulaire antd dans le tableau de bord utilisateur. Je me souviens qu'il ne peut pas être utilisé pour des composants purs. Est-ce possible maintenant ?
Je l'ai également rencontré le plus.S'il s'agit d'un composant de fonction pure, la fonction getFieldDecorator ne peut pas être obtenue via props.form.getFieldDecorator.Si vous utilisez des extensions pour créer un composant, vous pouvez l'obtenir.
Je ne sais pas si Dieu a une solution @sorrycc

Pouvez-vous s'il vous plaît lancer la même page en anglais ? Nous ne sommes pas en mesure de comprendre cela, et pourquoi avons-nous besoin de dva.

Bonjour, s'il s'agit d'un gros projet, son état sera très volumineux, et il sera très lourd à traiter, faut-il le scinder en plusieurs modèles ?

@yazhou-zyz j'ai le même problème que toi :
Avertissement : setState(...) : impossible de mettre à jour pendant une transition d'état existante (comme dans le rendu ou le constructeur d'un autre composant). Les méthodes de rendu doivent être une pure fonction des accessoires et de l'état ; les effets secondaires du constructeur sont un anti-modèle, mais peut être déplacé vers componentWillMount.
Je voudrais vous demander comment vous l'avez résolu?

Apprendre

continue d'étudier

dva est une grande valeur de référence pour les projets de construction.

bon travail ~

Où puis-je trouver les docs en anglais ??? Traduire le sujet avec des moteurs de traduction est problématique et la compréhension n'est pas suffisante. Avec l'anglais, vous pouvez atteindre le monde. Continuez votre bon travail !! :fusée:

dva n'est pas essayé dans les versions React-native 0.47.X et React16.0.0

@vecold a toujours pu l'utiliser, en disant que le code d'invitation ou le message d'erreur ne peut pas être utilisé

Y a-t-il une chance que nous puissions obtenir une traduction en anglais des documents ?
Merci!

Dans le code métier, un tel exemple est courant. Une mise à jour locale de l'état peut affecter tout le corps. De nombreux endroits qui n'ont pas besoin d'être re-rendus sont également re-rendus, ce qui réduit considérablement les performances de la page. Cette fonction peut-elle être ajoutée pour analyser automatiquement l'état dont dépend la connexion redux afin de réduire les calculs mapStateToProps inutiles et de restituer 👍

très bien
mais il construit toutes les pages quand j'espère construire une seule page

_traduction non officielle_

Pourquoi Dva ?

Redux est bon. Mais il y a trop de concepts, de réducteurs séparés, de sagas et d'actions (divisées en différents fichiers)

  1. Doit basculer fréquemment entre les réducteurs, les sages et les actions
  2. Gêne pour organiser les modèles d'affaires (ou modèles de domaine). Pour exp., lorsque nous avons déjà user_list et que product_list est requis, nous devons alors dupliquer une copie de fichier
  3. Saga est difficile à écrire. Vous devez faire fork -> watcher -> worker pour chaque action.
  4. La saisie est fastidieuse et compliquée

C'est quoi Dva ?

C'est un wrapper léger sur le framework existant (redux + react-router + redux-saga ...). Aucun nouveau concept impliqué. Code < 100 lignes. ( Inspiré par l'orme et le choo. )

C'est un framework, pas une bibliothèque. Comme Ember.js, il limite la façon dont vous écrivez chaque partie. C'est plus contrôlable pour le travail d'équipe. Dva encapsule toutes les dépendances à l'exception de react et react-dom en tant que peerDependencies

Son implémentation introduit le moins possible de nouvelles syntaxes. Il réutilise les dépendances. Pour exp., la définition du routeur est exactement de la même manière que le JSX de react-router.

La fonctionnalité de base est app.model . Il encapsule le réducteur, l'état initial, l'action et la saga.

app.model({
  namespace: 'products',
  state: {
    list: [],
    loading: false,
  },
  subscriptions: [
    function(dispatch) {
      dispatch({type: 'products/query'});
    },
  ],
  effects: {
    ['products/query']: function*() {
      yield call(delay(800));
      yield put({
        type: 'products/query/success',
        payload: ['ant-tool', 'roof'],
      });
    },
  },
  reducers: {
    ['products/query'](state) {
      return { ...state, loading: true, };
    },
    ['products/query/success'](state, { payload }) {
      return { ...state, loading: false, list: payload };
    },
  },
});

Nous avions l'habitude de créer sagas/products.js, reducers/products.js actions/products.js et de passer de l'un à l'autre.

point clé:

  • namespace : le key du reducer dans son objet rootReducer
  • État : initialState de reducer
  • abonnement : le nouveau concept d'[email protected] , exécuté lorsque dom est prêt : un adieu à FRP
  • effets : sauge plus facile
  • réducteurs

Comment utiliser

Voir des exemples

Feuille de route

  • devtool rechargement à chaud
  • Configuration dynamique pour le routeur
  • Effects prend en charge plus de modèles saga
  • Test de l'unité
  • Plus d'exemples : todolist, utilisateurs dans antd-init, produits populaires

FAQ

L'outil de développement prend en charge ?

Compatible avec redux-devtool, css livereload. Besoin de plus de travail pour le rechargement à chaud

Bon pour la prod env ?

Bien sur

Y compris toutes les fonctionnalités de redux + redux-saga ?

Oui

Compatibilité des navigateurs ?

Pas d'IE8 à cause de redux-saga. (Plus tard, peut appliquer thunk, promesse, observable en tant qu'extensions sur la couche d'effets)

s'il vous plaît similaire

['products/query']: function*() {}
['products/query'](state) {}

Quelle est la syntaxe ? Les tableaux peuvent-ils être utilisés comme noms de fonction ?

@clemTheDasher Le nom de la fonction peut être une clé calculée ( PAS de tableau) en JavaScript. Référence plus détaillée aux définitions de méthodes |

var obj = {
  property( parameters… ) {},
  *generator( parameters… ) {},
  async property( parameters… ) {},
  async* generator( parameters… ) {},

  // with computed keys:
  [property]( parameters… ) {},
  *[generator]( parameters… ) {},
  async [property]( parameters… ) {},

  // compare getter/setter syntax:
  get property() {},
  set property(value) {}
};

Rapports de nouveaux arrivants, venez ici et continuez à travailler dur pour acquérir des connaissances frontales

@clemTheDasher C'est une propriété calculée.

Apprendre!

regarde dieu

Dieu merci, merci pour l'open source

suis-je pas autorisé à apprendre de vous les gars!

J'ai appris, merci d'avoir un cadre aussi pratique à utiliser

Les liens de démonstration sur github ont expiré.

@sorrycc est-ce que dva prend en charge le rendu côté serveur maintenant ?

Le réducteur peut-il s'écrire ainsi :

const reducer = (state, { type, payload }) => {
  switch (type) {
    case 'products/query':
      return { ...state, loading: true, };
    case 'products/query/success':
      return { ...state, loading: false, list: payload };
    default
      return state;
  }
}

app.model({
  reducer
})

Cela permet d'appliquer certaines méthodes d'ordre supérieur au réducteur.

Le style d'écriture Redux est concis et une seule ligne est nécessaire pour modifier l'état, mais il semble que plusieurs lignes de code soient écrites ensemble via du sucre syntaxique. Mais je dois encore utiliser ...state pour livrer le statut restant au prochain arrêt, sinon le statut sera incomplet. En d'autres termes, pendant la phase de réduction, un état peut être perdu s'il est écrit de manière incorrecte.

À certains égards, l'idée de Vuex est plus facile à lire et plus naturelle. Écrivez quelque chose comme ça (pas exactement).

const mutation = {
  ['products/query'](state) {
    state.loading = true
  },
  ['products/query/success'](state, payload) {
    state.loading = false
    state.list = payload
  }
}

Du point de vue du code, je ne me soucie que de l'état que je modifie (de manière synchrone). Vuex devrait également envelopper une couche à l'extérieur pour la livraison de l'état suivant. Il est possible que des vérifications défensives (devinettes) soient également effectuées avant la livraison, ou que des hameçons soient plantés.

Demandez-moi si la page d'exemple du site officiel de dva ne peut pas sortir et signaler une erreur, s'agit-il d'une mise à niveau ?

s'il vous plaît similaire

['products/query']: function*() {}
['products/query'](state) {}

Quelle est la syntaxe ? Les tableaux peuvent-ils être utilisés comme noms de fonction ?

ES6 permet aux littéraux de définir des objets, (expression) comme nom de propriété de l'objet, c'est-à-dire de mettre l'expression entre crochets.
Comme

obj = {
  ['xxname']: 'what ever you defined',
  ['xxyy'](args) {
    ....
  }
}

Il y a une question, "products/query" est utilisé pour traiter l'appel des réducteurs, et il est associé à l'espace de noms via une chaîne. Plus tard, si le projet devient plus grand, comme des centaines de méthodes. Si mon namspace doit être changé. Changer cent méthodes ?

@yesmeck 👍, le rôle de réducteur amplificateur a été sous-estimé auparavant.

Vous ne savez pas s'il y a du support ici ?

Cette page vous a été utile?
0 / 5 - 0 notes