redux todomvc:新项目时会发生什么?

redux todomvc: what happens when new one item?

我正在学习redux todomvc,对下面的源代码有一些疑问。欢迎任何意见。谢谢

Q1: 为什么 store.dispatch() 和 store.subscribe() 没有被调用?看来这个例子和这里的data flow introduction有点不一样。

Q2:谁能解释一下当新的一个项目时会发生什么? src/index.js, src/containers/App.js, src/components/Header.js, src/components/TodoTextInput.js 如何在新项目时一起工作?

Q3:todos和actions来自(src/containers/App.js)在哪里?

Q4:这个状态===store.getState()(在src/components/TodoTextInput.js)?

// src/index.js
import React from 'react'
import { render } from 'react-dom'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import App from './containers/App'
import reducer from './reducers'
import 'todomvc-app-css/index.css'

const store = createStore(reducer)

render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

// src/containers/App.js
import React, { PropTypes } from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import Header from '../components/Header'
import MainSection from '../components/MainSection'
import * as TodoActions from '../actions'

const App = ({todos, actions}) => (//Q3: where are todos and actions from?
  <div>
    <Header addTodo={actions.addTodo} />  
    <MainSection todos={todos} actions={actions} />
  </div>
)

App.propTypes = {
  todos: PropTypes.array.isRequired,
  actions: PropTypes.object.isRequired
}

const mapStateToProps = state => ({
  todos: state.todos
})

const mapDispatchToProps = dispatch => ({
    actions: bindActionCreators(TodoActions, dispatch)
})

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(App)


// src/components/Header.js
import React, { PropTypes, Component } from 'react'
import TodoTextInput from './TodoTextInput'

export default class Header extends Component {
  static propTypes = {
    addTodo: PropTypes.func.isRequired
  }

  handleSave = text => {
    if (text.length !== 0) {
      this.props.addTodo(text)
    }
  }

  render() {
    return (
      <header className="header">
        <h1>todos</h1>
        <TodoTextInput newTodo// where is it from?
                       onSave={this.handleSave}
                       placeholder="What needs to be done?" />
      </header>
    )
  }
}

// src/components/TodoTextInput.js
import React, { Component, PropTypes } from 'react'
import classnames from 'classnames'

export default class TodoTextInput extends Component {
  static propTypes = {
    onSave: PropTypes.func.isRequired,
    text: PropTypes.string,
    placeholder: PropTypes.string,
    editing: PropTypes.bool,
    newTodo: PropTypes.bool
  }

  state = {//Q4: this state === store.getState()?
    text: this.props.text || ''
  }

  handleSubmit = e => {
    const text = e.target.value.trim()
    if (e.which === 13) {
      this.props.onSave(text)
      if (this.props.newTodo) {
        this.setState({ text: '' })
      }
    }
  }

  handleChange = e => {
    this.setState({ text: e.target.value })
  }

  handleBlur = e => {
    if (!this.props.newTodo) {
      this.props.onSave(e.target.value)
    }
  }

  render() {
    return (
      <input className={
        classnames({
          edit: this.props.editing,
          'new-todo': this.props.newTodo
        })}
        type="text"
        placeholder={this.props.placeholder}
        autoFocus="true"
        value={this.state.text}
        onBlur={this.handleBlur}
        onChange={this.handleChange}
        onKeyDown={this.handleSubmit} />
    )
  }
}

// src/components/TodoItem.js
import React, { Component, PropTypes } from 'react'
import classnames from 'classnames'
import TodoTextInput from './TodoTextInput'

export default class TodoItem extends Component {
  static propTypes = {
    todo: PropTypes.object.isRequired,
    editTodo: PropTypes.func.isRequired,
    deleteTodo: PropTypes.func.isRequired,
    completeTodo: PropTypes.func.isRequired
  }

  state = {
    editing: false
  }

  handleDoubleClick = () => {
    this.setState({ editing: true })
  }

  handleSave = (id, text) => {
    if (text.length === 0) {
      this.props.deleteTodo(id)
    } else {
      this.props.editTodo(id, text)
    }
    this.setState({ editing: false })
  }

  render() {
    const { todo, completeTodo, deleteTodo } = this.props

    let element
    if (this.state.editing) {
      element = (
        <TodoTextInput text={todo.text}
                       editing={this.state.editing}
                       onSave={(text) => this.handleSave(todo.id, text)} />
      )
    } else {
      element = (
        <div className="view">
          <input className="toggle"
                 type="checkbox"
                 checked={todo.completed}
                 onChange={() => completeTodo(todo.id)} />
          <label onDoubleClick={this.handleDoubleClick}>
            {todo.text}
          </label>
          <button className="destroy"
                  onClick={() => deleteTodo(todo.id)} />
        </div>
      )
    }

    return (
      <li className={classnames({
        completed: todo.completed,
        editing: this.state.editing
      })}>
        {element}
      </li>
    )
  }
}

Q1 why store.dispatch() and store.subscribe() not called?

因为'container'。在 redux 中,容器是一个订阅 store 变化的组件。这是通过 Redux 的 mapStateToPropsmapDispatchToProps 和最后 container file

中的 connect 函数调用完成的

connect 函数在内部调用 store.subscribe

Q2: can anyone explain what happens when new one item?

  1. App 容器通过 mapDispatchToProps

  2. actions 属性传递给 App 组件
  3. 这个道具 actions 包含动作 addTodo 并且向下传递到 Header

  4. Header 组件在 TextInput 上调用 addTodo 操作是 saved

  5. 动作addTodo被分派

  6. reducer handles 动作并用新项目更新状态。商店已更新。

  7. 商店更新触发 App 容器使用更新的道具重新渲染,因为 App 容器有 mapStateToProps

  8. 完成

Q3: where are todos and actions from (src/containers/App.js)?

同样,这是因为 Redux 的 connect 功能。它将从 mapStateToPropsmapDispatchToProps 获取返回值,合并它们并将其作为 props 传递给 App 组件。 todos 来自 mapStateToPropsactions 来自 mapDispatchToProps

Q4 this state === store.getState()

不要混淆。 TodoTextInput 中的 state 是 React 的原生组件状态,与 Redux 的状态无关。但是,如果您的应用程序需要状态,通常需要决定它是应该存在于 Redux 存储中还是应该存在于组件本身中。

如果状态只与组件本身相关,没有其他组件需要知道该状态的状态,则表明它应该存在于组件内部而不是在 Redux 的商店中。

TodoTextInput 组件中的状态在用户提交更改之前暂时保留用户输入。它很适合成为组件本身的内部状态。