<p>Introdução ao DVA</p>

Criado em 24 jun. 2016  ·  76Comentários  ·  Fonte: dvajs/dva

Não há conceitos novos, todos são antigos.

Por que dva?

Após um período de autoestudo ou treinamento, todos devem ser capazes de entender o conceito de redux e reconhecer que esse controle de fluxo de dados pode tornar a aplicação mais controlável e a lógica mais clara.

Mas geralmente há essa pergunta: há muitos conceitos e redutor, saga e ação são todos separados (sub-arquivos).

O problema com isso é:

  • O custo de edição é alto e você precisa alternar entre redutor, saga e ação
  • Não é conveniente organizar o modelo de negócios (ou modelo de domínio). Por exemplo, depois de escrever uma lista de usuários, para escrever uma lista de produtos, precisamos copiar muitos arquivos.

E alguns outros:

  • A escrita da saga é muito complicada. Toda vez que você ouve uma ação, você precisa passar pelo processo de fork -> watcher -> worker
  • problema de escrita de entrada
  • ...

E o dva é usado para resolver esses problemas.

O que é dva?

dva é um pacote leve baseado na arquitetura de aplicação existente (redux + react-router + redux-saga, etc.), sem introduzir novos conceitos, e o código total é inferior a 100 linhas. (Inspirado por olmo e choo.)

dva é um framework, não uma biblioteca. Semelhante ao emberjs, ele irá dizer claramente como cada componente deve ser escrito, o que é mais controlável para a equipe. Além disso, dva encapsula todas as outras dependências, exceto react e react-dom, que são peerDependencies.

Na implementação do dva, tente não criar uma nova sintaxe, mas use a sintaxe da própria biblioteca de dependências, como a definição de roteador ou a sintaxe JSX de react-router (a configuração dinâmica é uma consideração de desempenho, que será suportada mais tarde).

O núcleo disso é fornecer o método app.model , que é usado para encapsular redutor, initialState, ação e saga juntos, como:

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 };
    },
  },
});

Antes do dva, normalmente criamos sagas/products.js , reducers/products.js e actions/products.js e depois alternamos entre esses arquivos.

Apresente as chaves desses modelos: (supondo que você já esteja familiarizado com a arquitetura de aplicativos do redux, redux-saga)

  • namespace - corresponde ao valor da chave do redutor ao combinar com o rootReducer
  • state - corresponde ao initialState do redutor
  • assinatura - um novo conceito de [email protected] , executado após dom pronto, sem explicação aqui, veja: A Farewell to FRP
  • efeitos - corresponde à saga e simplifica o uso
  • redutores

Como usar

Consulte exemplos:

Roteiro

  • [x] suporte para troca a quente devtool
  • [x] O roteador suporta configuração dinâmica
  • [x] Os efeitos precisam suportar mais modos de saga
  • [ ] Efeitos considera estender o acesso a soluções thunk, promessa, observável e outras, o objetivo básico é ser compatível com o IE8
  • [ ] É muito problemático passar despacho entre componentes, considere o seguinte plano
  • [x] Solução de teste de unidade
  • [x] Mais exemplos: todolist, usuários em antd-init, produtos populares

    Perguntas frequentes

Suporte no nível da ferramenta de desenvolvimento?

Além da substituição a quente, que ainda não foi adaptada, outras como redux-devtool, css livereload, etc. são todas compatíveis.

Já está disponível para o ambiente de compilação?

Pode.

Inclui todos os recursos da arquitetura de aplicativos redux + redux-saga anterior?

sim.

Compatibilidade do navegador?

O IE8 não suporta porque o redux-saga é usado. (Consideraremos o suporte a thunks, promessas, observáveis, etc. na camada de efeitos de maneira estendida posteriormente)

Comentários muito úteis

Ser vivificado pelo redux é simplesmente gospel. É muito simples e elegante. Grande elogio! ! !

btw, eu acidentalmente vi um estrangeiro repostando no Twitter hoje, pensei que fosse escrito por um estrangeiro, mas não esperava que fosse um colega de classe do Alipay, 👍

Todos 76 comentários

Ser vivificado pelo redux é simplesmente gospel. É muito simples e elegante. Grande elogio! ! !

btw, eu acidentalmente vi um estrangeiro repostando no Twitter hoje, pensei que fosse escrito por um estrangeiro, mas não esperava que fosse um colega de classe do Alipay, 👍

Ansioso para a expansão dos efeitos

O ambiente de produção Alipay está usando essa arquitetura?

O @besteric dva acabou de sair e ainda não foi aplicado, mas a arquitetura do aplicativo por trás dele já é usada há algum tempo.

O redutor pode ser escrito assim:

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
})

Isso torna possível aplicar alguns métodos de ordem superior ao redutor.

Elogios, eu escrevi algumas demos e há apenas um problema, o modelo só pode ser usado
app.model(Model1); app.model(Model2);
É este método para completar a combinação, na verdade, acho que o ideal é
app.model([Model1,Model2])
algum tipo de

É muito problemático passar despacho entre componentes, considere a seguinte solução

Não use bindActionCreators ?

O cenário específico do uso avançado do redutor @yesmeck é apenas redo/undo ?Não quero que o dva seja muito flexível e considerarei adicioná-lo por meio do complemento no futuro.

Usamos muito em nosso projeto. Por exemplo, vamos extrair as partes semelhantes de vários redutores em um método de alto nível para modificar o redutor original, e existem métodos de alto nível que permitem que o redutor redefina o estado quando a rota changes. , e este https://github.com/erikras/multireducer

@ Tinker404 Acho que seria mais claro declarar o modelo separadamente e seria mais fácil adicionar e excluir. Eu escreveria isso:

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

@JimmyLv pessoalmente prefere não usar actionCreator, mas apenas dispatch .

@yesmeck ok, vou pensar sobre isso novamente.

Existem também métodos de ordem superior que permitem que o redutor redefina o estado quando a rota muda

Eu sinto que esse cenário é mais apropriado assinando as alterações de roteamento em subscriptions e redefinindo o estado por meio de ação. Ou há alguma vantagem em usar o método de intensificador redutor?

Eu sinto que esse cenário é mais apropriado assinando as alterações de roteamento nas assinaturas e, em seguida, redefinindo o estado por meio de ação

Nesse caso, cada redutor que precisa ser resetado precisa escrever a lógica de reset. Se usarmos um método de alto nível, só precisamos fazer isso agora:

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

Outro cenário é a mesma lógica que estou falando em extrair diferentes redutores. Por exemplo, existe uma lista de produtos e uma lista de usuários, e seus redutores são assim:

// 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
  }
}

Aqui os dois redutores são quase os mesmos, então extraímos e escrevemos um redutor de lista:

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

Em seguida, implementamos um composeReducers para combinar esses 3 redutores:

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))
  }
}

Dessa forma, o redutor para a lista de produtos e a lista de usuários se torna este:

// 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 é apenas um exemplo, na verdade, existem muitos redutores no projeto que possuem a mesma lógica.

@yesmeck 👍, o papel do potenciador redutor já foi subestimado antes.

@sorrycc você pode dizer por quê? Chamado explicitamente com comparação dispatch ?

@ Tinker404 Acho que seria mais claro declarar o modelo separadamente e seria mais fácil adicionar e excluir. Eu escreveria isso:
app.model(require('../models/a'));
app.model(require('../models/b'));

Também sugiro um método que pode passar vários modelos ao mesmo tempo. Projetos grandes podem ter muitos modelos. Agora preciso (importar) todos eles e depois modelar cada modelo um por um, o que não é muito conveniente. Minha maneira atual da escrita é:

// 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;

É muito D.VA.

Encontrei o uso do componente antd form no user-dashboard. Lembro que não pode ser usado para componente puro. É possível agora?

@codering Não me lembro de haver restrições, problemas com antd podem ser solicitados em https://github.com/ant-design/ant-design/issues .

Olá, eu quero usar seu dva. Atualmente, eu uso a estrutura de diretórios gerada pelo scaffolding React Webpack Redux. Eu alterei o código com referência ao exemplo do painel de usuário no seu exemplo, mas não há nada após o início. Você pode ajudar me descobrir onde está? Algo deu errado, meu endereço do projeto: https://github.com/baiyulong/lenovo_parts

@baiyulong por que não fazê-lo diretamente com base na estrutura de diretórios do painel do usuário?

@sorrycc Estou usando a estrutura de diretórios do painel de usuário agora. Existe algum tratamento especial ou gravação para roteamento dva?
export default function({ history }) {
return (
<Router history={history}>
<IndexRoute component={HomePage} />
<Route path='/' component={HomePage}>
<Route path='/create' component={CreateOrder} />
</Route>
</Router>
)
}
Esta rota eu escrevi, HomePage pode, escrevi um link <Link to='/create'>Create</Link> , não consigo ir para o componente CreateOrder depois de clicar nele

Não há uma maneira especial de escrever a rota de @baiyulong dva , tente:

  1. Existe um erro
  2. Tente acessar a rota /create diretamente

@nikogu muito obrigado, ficarei bem depois de aninhar

Olá, o dva pode suportar carregamento a quente de modelos?

@kkkf1190 está considerando isso e irá apoiá-lo.

👍

Só queria dizer obrigado. . .

Sempre achei muito bom o andaime do vue-cli do vuejs. Depois de ler isso, meu pensamento mudou completamente.

Quadro muito maravilhoso! Faz um tempo que estou pesquisando. @sorrycc Quero fazer duas perguntas a Yunda:

  1. O dva pode ser perfeitamente usado em projetos nativos de reação?
  2. O dva + reactjs pode suportar bem a renderização do lado do servidor?

@freemember007

  1. Suporte react-native, exemplo de referência: https://github.com/sorrycc/dva-example-react-native
  2. Não há nenhum problema na operação do servidor em teoria. Tanto o redux quanto o react-router por trás dele suportam SSR, mas levará algum tempo para aplicá-lo ao dva, porque a lógica relevante deve ser corrigida e bem empacotada

@sorrycc Existe uma solução para suporte a redutores de ordem superior agora? Nosso projeto usa muitos redutores de alta ordem devido à reutilização

Suportado por @ancheel , pode ser global ou local, caso de uso de referência: https://github.com/dvajs/dva/blob/master/test/reducers-test.js

Depois que o estado do modelo é modificado, como modificá-lo novamente, esse problema sempre ocorre agora
antd.js:32924 Aviso: setState(...): Não é possível atualizar durante uma transição de estado existente (como em render ou no construtor de outro componente). Os métodos de renderização devem ser uma função pura de props e state; constructor os efeitos colaterais são um antipadrão, mas podem ser movidos para componentWillMount .

Muito emocionante, tente usá-lo no ambiente de produção, espero continuar otimizando e melhorando

nerfa isso!

Bom trabalho。Obrigado!!

@sorrycc Ansioso para oferecer suporte à renderização do lado do servidor!

Suportado por @mountainmoon , consulte https://github.com/sorrycc/dva-boilerplate-isomorphic .

Uma onda de rodas veio :+1:

Olá, acabei de entrar em contato com o aprendizado deste dva. Depois de ler o código por alguns dias, tenho algumas dúvidas em meu coração. Gostaria de perguntar:
Vi que seus demos são todos aplicativos de página única, mas todos são aplicativos de várias páginas em desenvolvimento. Gostaria de perguntar se o roteamento não é usado no desenvolvimento de aplicativos de várias páginas, como carregar componentes, talvez eu esteja perguntando a um idiota. É um pouco confuso, porque eu não uso roteamento, então o listener definido nos modelos não fica claro onde acionar:
history.listen( local => {
if(location.pathname === '/users') {
Despacho({
type:'querySuccess',
carga útil:{}
})
}
})
PS: Ao carregar dados no método querySuccess e usar export default connect(mapStateToProps)(Users); também é relatado um erro:
connect.js:41 Uncaught TypeError: não é possível chamar uma classe como uma função
Eu me sinto um idiota em um instante, não sei se posso incomodá-lo para me explicar, obrigado!

Por que dva? inglês, por favor

Não gosto muito dessa forma de escrever.

@codering você mencionou o uso de componentes de formulário antd no user-dashboard. Lembro que não pode ser usado para componentes puros. É possível agora?
Eu também encontrei mais.Se for um componente de função pura, a função getFieldDecorator não pode ser obtida através de props.form.getFieldDecorator.Se você usar extends para criar um componente, você pode obtê-lo.
Não sei se Deus tem solução @sorrycc

Você pode, por favor, lançar a mesma página em inglês? Não somos capazes de entender isso, e por que precisamos de dva.

Olá, se for um projeto grande, seu estado será muito grande e será muito trabalhoso para processar. Deveria ser dividido em vários modelos?

@yazhou-zyz eu tenho o mesmo problema que você:
Aviso: setState(...): Não é possível atualizar durante uma transição de estado existente (como dentro de render ou construtor de outro componente). Métodos de renderização devem ser uma função pura de props e state; os efeitos colaterais do construtor são um anti-padrão, mas pode ser movido para componentWillMount.
Gostaria de saber como você resolveu?

Aprender

continue estudando

dva é de grande valor de referência para projetos de construção.

bom trabalho~

Onde posso encontrar os documentos em inglês ??? Traduzir o tópico com mecanismos de tradução é problemático e o entendimento não é suficiente. Com o inglês, vocês podem alcançar o mundo. Mantenha o bom trabalho!! :foguete:

dva não é testado nas versões 0.47.X e React16.0.0 nativas do React

@vecold sempre foi capaz de usá-lo, dizendo que o código de convite ou mensagem de erro não pode ser usado

Existe uma chance de conseguirmos a tradução em inglês dos documentos?
Obrigado!

No código comercial, esse exemplo é comum. Uma atualização de estado local pode afetar todo o corpo. Muitos locais que não precisam ser renderizados novamente também são renderizados novamente, o que reduz muito o desempenho da página. Essa função pode ser adicionada para analisar automaticamente o estado do qual o redux connect depende para reduzir cálculos desnecessários de mapStateToProps e renderizar novamente 👍

muito bom
mas constrói toda a página quando espero construir uma única página

_tradução não oficial_

Por que Dva?

Redux é bom. Mas há muitos conceitos, redutores separados, sagas e ações (divididas em arquivos diferentes)

  1. Deve alternar entre redutores, sábios e ações com frequência
  2. Inconveniência para organizar modelos de negócios (ou modelos de domínio). Por exemplo, quando já temos user_list e product_list é necessário, deve-se duplicar uma cópia do arquivo
  3. Saga é difícil de escrever. Você deve fazer fork -> watcher -> worker para cada ação.
  4. A entrada é tediosa e complicada

O que é Dva?

É um wrapper lite sobre o framework existente (redux + react-router + redux-saga ...). Nenhum novo conceito envolvido. < 100 linhas de código. (Inspirado por olmo e choo.)

É um framework, não uma biblioteca. Assim como o Ember.js, ele restringe a maneira como você escreve cada parte. É mais controlável para o trabalho em equipe. Dva encapsula todas as dependências exceto react e react-dom como peerDependencies

Sua implementação introduz novas sintaxes o menos possível. Ele reutiliza as dependências. Por exemplo, a definição do roteador é exatamente da mesma maneira que o JSX do roteador de reação.

A funcionalidade principal é app.model . Ele encapsula o redutor, o initialState, a ação e a saga completamente.

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 };
    },
  },
});

Costumávamos criar sagas/products.js, reducers/products.js actions/products.js e alternar entre eles.

ponto chave:

  • namespace: o key do reducer em seu objeto rootReducer
  • estado: initialState de reducer
  • assinatura: o novo conceito de [email protected] , executado quando o dom estiver pronto: A Farewell to FRP
  • efeitos: sálvia mais fácil
  • redutores

Como usar

Veja exemplos

Roteiro

  • devtool recarga a quente
  • Configuração dinâmica para roteador
  • Effects suporta mais modelos saga
  • Teste de unidade
  • Mais exemplos: todolist, usuários no antd-init, produtos populares

Perguntas frequentes

Suporte para ferramentas de desenvolvimento?

Compatível com redux-devtool, css livereload. Precisa de mais trabalho para recarga a quente

Bom para prod env?

certo

Incluindo todas as funcionalidades do redux + redux-saga?

sim

Compatibilidade de navegadores?

Sem IE8 devido ao redux-saga. (Mais tarde pode aplicar thunk, promessa, observável como extensões na camada de efeitos)

por favor semelhante

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

Qual é a sintaxe? Os arrays podem ser usados ​​como nomes de funções?

@clemTheDasher O nome da função pode ser uma chave computada ( NÃO array) em JavaScript. Referência mais detalhada às definições de Método |

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) {}
};

Relatórios de recém-chegados, venha aqui e continue trabalhando duro para aprender o conhecimento de front-end

@clemTheDasher Essa é a propriedade computada.

Aprender!

olhar para Deus

Graças a Deus, obrigado pelo código aberto

não estou autorizado a aprender com vocês!

Eu aprendi, obrigado por ter uma estrutura tão conveniente para nós usarmos

Os links de demonstração no github expiraram.

@sorrycc o dva suporta renderização no lado do servidor agora?

O redutor pode ser escrito assim:

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
})

Isso torna possível aplicar alguns métodos de ordem superior ao redutor.

O estilo de escrita Redux é conciso, e apenas uma linha é necessária para modificar o estado, mas parece que várias linhas de código são escritas juntas através do açúcar sintático. Mas ainda preciso usar ...state para entregar o status restante para a próxima parada, caso contrário o status ficará incompleto. Em outras palavras, durante a fase de redução, algum estado pode ser perdido se for escrito incorretamente.

De certa forma, a ideia do Vuex é mais fácil de ler e mais natural. Escreva algo assim (não exatamente).

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

Em termos de código, eu só me importo com o estado que eu (sincronamente) modifico. O Vuex também deve envolver uma camada externa para entrega na próxima parada do estado. Possivelmente, algumas verificações defensivas (suposições) também são feitas antes da entrega, ou ganchos são colocados.

Pergunte-me se a página de exemplo do site oficial da dva não pode sair e relatar um erro, é uma atualização?

por favor semelhante

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

Qual é a sintaxe? Os arrays podem ser usados ​​como nomes de funções?

O ES6 permite que literais definam objetos, (expressão) como o nome da propriedade do objeto, ou seja, coloque a expressão entre colchetes.
Tal como

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

Existe uma dúvida, 'produtos/consulta' é usado para processar a chamada de redutores, e é associado ao namespace através de uma string, posteriormente, se o projeto se tornar maior, como centenas de métodos. Se meu namspace tiver que ser alterado. Para mudar uma centena de métodos?

@yesmeck 👍, o papel do potenciador redutor já foi subestimado antes.

Não sabe se há suporte aqui?

Esta página foi útil?
0 / 5 - 0 avaliações