shoutingwei / react-learning

take notes for redux learning
0 stars 0 forks source link

Redux #11

Open shoutingwei opened 6 years ago

shoutingwei commented 6 years ago

npm install --save redux npm install --save react-redux npm install --save-dev redux-devtools

// 首先定义一个改变数据的plain函数,成为reducer
function count (state, action) {
  var defaultState = {
    year: 2015,
  };
  state = state || defaultState;
  switch (action.type) {
    case 'add':
      return {
        year: state.year + 1
      };
    case 'sub':
      return {
        year: state.year - 1
      }
    default :
      return state;
    }
  }

// store的创建
var createStore = require('redux').createStore;
var store = createStore(count);

// store里面的数据发生改变时,触发的回调函数
store.subscribe(function () {
  console.log('the year is: ', store.getState().year);
});

// action: 触发state改变的唯一方法(按照redux的设计思路)
var action1 = { type: 'add' };
var action2 = { type: 'add' };
var action3 = { type: 'sub' };

// 改变store里面的方法
store.dispatch(action1); // 'the year is: 2016
store.dispatch(action2); // 'the year is: 2017
store.dispatch(action3); // 'the year is: 2016

你应该把要做的修改变成一个普通对象,这个对象被叫做 action,而不是直接修改 state。然后编写专门的函数来决定每个 action 如何改变应用的 state,这个函数被叫做 reducer。

如果你以前使用 Flux,那么你只需要注意一个重要的区别。Redux 没有 Dispatcher 且不支持多个 store。相反,只有一个单一的 store 和一个根级的 reduce 函数(reducer)。随着应用不断变大,你应该把根级的 reducer 拆成多个小的 reducers,分别独立地操作 state 树的不同部分,而不是添加新的 stores。这就像一个 React 应用只有一个根级的组件,这个根组件又由很多小组件构成。

唯一的stroe,但可以有多个reducer。

shoutingwei commented 6 years ago

createStore原理

export default function createStore(reducer, initialState) {
  ...
  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer
  }
}

dispatch: 用于action的分发,改变store里面的state subscribe: 注册listener,store里面state发生改变后,执行该listener getState: 读取store里面的state replaceReducer: 替换reducer,改变state修改的逻辑

shoutingwei commented 6 years ago

combineReducer

import { combineReducers, createStore } from 'redux'
let reducer = combineReducers({ visibilityFilter, todos })
let store = createStore(reducer)
shoutingwei commented 6 years ago

connect

import React, { Component } from 'react'
import { connect } from 'react-redux'

import { sendMessage, deleteMessage } from './actions'

class SomeComponent extends Component {
  handleSend = message => {
 ###    this.props.sendMessage(message)
  }
  handleDelete = id => {
###     this.props.deleteMessage(id)
  }
  render() {
    // ...
  }
}
const mapStateToProps = state => state
// ▽ ▽ ▽ ▽ ▽ ▽ ▽ ▽ ▽ ▽
const mapDispatchToProps = /* this part is to be discussed */
// △ △ △ △ △ △ △ △ △ △
export default connect(
  mapStateToProps,
  mapDispatchToProps,//接受一个对象或者返回一个对象的函数,这个对象都是函数,函数名为key,值为函数
)(SomeComponent)

mapDispatchToProps 方法1 用函数直接绑定

const mapDispatchToProps = dispatch => ({
  sendMessage: message => dispatch(sendMessage(message)),
  deleteMessage: id => dispatch(deleteMessage(id)),
})

方法2 直接用对象

const mapDispatchToProps = {
  sendMessage, // will be wrapped into a dispatch call
  deleteMessage, // will be wrapped into a dispatch call
};
import * as messageActions from './messageActions'
import * as userActions from './userActions'
// ...
const mapDispatchToProps = {
  ...messageActions,
  ...userActions,
};

方法3 bindActionCreators

import { bindActionCreators } from 'redux'
// ...
const mapDispatchToProps = dispatch => bindActionCreators(
  {
    sendMessage,
    deleteMessage,
  },
  dispatch,
)

bindActionCreators原理

export default function bindActionCreators(actionCreators, dispatch) {//返回多个函数构成的对象
  if (typeof actionCreators === 'function') { // #1
    return bindActionCreator(actionCreators, dispatch) // #2 单个函数
  }

  ....

  const keys = Object.keys(actionCreators)
  const boundActionCreators = {}
  for (let i = 0; i < keys.length; i++) {
    const key = keys[i]
    const actionCreator = actionCreators[key]
    if (typeof actionCreator === 'function') { // #3
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch) //多个函数
    }
  }
  return boundActionCreators
}

function bindActionCreator(actionCreator, dispatch) { //高阶函数 HOC 返回函数
  return (...args) => dispatch(actionCreator(...args))
}

//如果不用connect 直接调用
const MyNewActionCreators = bindActionCreators(actionCreator, dispatch);

MyNewActionCreators.increment() // { type: 'INCREMENT', step: 1 }
MyNewActionCreators.increment(2) // { type: 'INCREMENT', step: 2 }
MyNewActionCreators.increment(3) // { type: 'INCREMENT', step: 3 }
MyNewActionCreators.decrement() // { type: 'DECREMENT', step: -1 }
MyNewActionCreators.decrement(2) // { type: 'DECREMENT', step: -2 }
MyNewActionCreators.decrement(3) // { type: 'DECREMENT', step: -3 }

//如果不用bindActionCreators 直接调用

dispatch(MyActionCreators.increment()) // { type: 'INCREMENT', step: 1 }
dispatch(MyActionCreators.increment(2)) // { type: 'INCREMENT', step: 2 }
dispatch(MyActionCreators.increment(3)) // { type: 'INCREMENT', step: 3 }
dispatch(MyActionCreators.decrement()) // { type: 'DECREMENT', step: -1 }
dispatch(MyActionCreators.decrement(2)) // { type: 'DECREMENT', step: -2 }
dispatch(MyActionCreators.decrement(3)) // { type: 'DECREMENT', step: -3 }
import { bindActionCreators } from 'redux'
// ...
const mapDispatchToProps = dispatch => ({
  ...bindActionCreators(
    {
      sendMessage,
      deleteMessage,
    },
    dispatch,
  ),//返回对象 并展开
  otherService, // this is not to be wrapped into dispatch
})

代码#1的判断语句是为了做兼容处理,当接收的参数actionCreators为一个函数的时候,则认为它是单一的动作工厂,便在代码#2处直接调用bindActionCreator工具函数来封装调度过程。

另一情况是当actionCreators参数是一个对象的时候,则遍历这个对象。代码#3会判断每个键在原始对象中的值是否是个函数,如果是一个函数则认为它是一个动作工厂,并使用bindActionCreator函数来封装调度过程,最后把生成的新函数以同样的键key存储到boundActionCreators对象中。 在函数的末尾会返回boundActionCreators对象。

shoutingwei commented 6 years ago

actionCreator

const counterIncActionCreator = function(step) {
  return {
    type: 'INCREMENT',
    step: step || 1
  }
}
shoutingwei commented 6 years ago

mapStateToProps(store.getState(), ownProps)

const mapStateToProps = (state) => {
    return {
        counter: state.counter
    }
};
const connect = (mapStateToProps) => (SomeComponent) => {
  class Connect extends Component {
    static contextTypes = {
      store: PropTypes.object
    }

    constructor () {
      super()
      this.state = { allProps: {} }
    }

    componentWillMount () {
      const { store } = this.context
      this.setProps()
    }

    setProps () {
      const { store } = this.context

   //this.props 为什么this能获取到SomeComponent上的props
      **let stateProps = mapStateToProps(store.getState(), this.props) // SomeComponent上额外传入 props**
      this.setState({
        allProps: { // 整合普通的 props 和从 state 生成的 props
          ...stateProps,
          ...this.props
        }
      })
    }

    render () {
      return <SomeComponent {...this.state.allProps} />
    }
  }
  **return Connect**
}
shoutingwei commented 6 years ago

mapStateToProps, mapDispatchToProps

const connect = (mapStateToProps, mapDispatchToProps) => (SomeComponent) => {
  class Connect extends Component {
    static contextTypes = {
      store: PropTypes.object
    }

    constructor () {
      super()
      this.state = {
        allProps: {}
      }
    }

    componentWillMount () {
      const { store } = this.context
      this.setProps()
      store.subscribe(() => this.setProps()) //一旦变化, props更新
    }

    setProps () { // 做了一下完整性的判断
      const { store } = this.context
      let stateProps = mapStateToProps
        **? mapStateToProps(store.getState(), this.props)**
        : {} // 防止 mapStateToProps 没有传入
      let dispatchProps = mapDispatchToProps
        **? mapDispatchToProps(store.dispatch, this.props)**
        : {} // 防止 mapDispatchToProps 没有传入
      this.setState({
        allProps: {
          **...stateProps,
          ...dispatchProps,**
          ...this.props
        }
      })
    }

    render () {
      return <SomeComponent {...this.state.allProps} />
    }
  }
  return Connect
}
shoutingwei commented 6 years ago

connect中第三个参数 [mergeProps(stateProps, dispatchProps, ownProps): props] (Function):

如果指定了这个参数,mapStateToProps() 与 mapDispatchToProps() 的执行结果和组件自身的 props 将传入到这个回调函数中。

该回调函数返回的对象将作为 props 传递到被包装的组件中。你也许可以用这个回调函数,根据组件的 props 来筛选部分的 state 数据,或者把 props 中的某个特定变量与 action creator 绑定在一起。如果你省略这个参数,

默认情况下返回 Object.assign({}, ownProps, stateProps, dispatchProps) 的结果。

mergeProps还是一个函数,用来筛选哪些参数传递给组件。这个函数接受3个参数。

const defaultMergeProps = (stateProps, dispatchProps, ownProps) => ({
  ...parentProps,
  ...stateProps,
  ...dispatchProps
})
shoutingwei commented 6 years ago

connect的第四个参数 [options] (Object) 如果指定这个参数,可以定制 connector 的行为。

options,这个是一个对象,有两个布尔,一个是pure,一个是withRef。 如果pure为false,只要connect接受到属性,不管是否有变化,必然刷新connect组件。 withRef为true时,在装饰传入的 React 组件时,Connect 会保存一个对该组件的 refs 引用,可以通过 getWrappedInstance 方法来获得该 refs,并最终获得原始的 DOM 节点。

shoutingwei commented 6 years ago

https://juejin.im/entry/5947702461ff4b006cf8ff86 connect全解

shoutingwei commented 6 years ago

Provider

provider用来给react组件提供数据,数据放在context中的store键。源码非常简单。

const App = () => {
  return (
    <Provider store={store}>
      <Comp/>
    </Provider>
  )
};
function createProvider(storeKey = 'store', subKey) {
    const subscriptionKey = subKey || `${storeKey}Subscription`//subscriptionKey有啥用??

    class Provider extends Component {
       // 拿到传入的store直接挂在到当前store上
        **constructor(props, context) {**
          super(props, context)
          this[storeKey] = props.store;//为啥不是props[storeKey]
        }

        // getChildContext: 将store传递给子孙component
        getChildContext() {
          return { [storeKey]: this[storeKey], [subscriptionKey]: null }
        }

        render() {
//this.props.children 是Provider内的子元素,Children.only用来Verifies that children has only one child //(a React element) and returns it. Otherwise this method throws an error.

         return Children.only(this.props.children);

        }
    }

    if (process.env.NODE_ENV !== 'production') {
      **Provider.prototype.componentWillReceiveProps = function (nextProps) {**
        // 如果store有改变,则发出警告
        if (this[storeKey] !== nextProps.store) {//store引用是不会变的
          warnAboutReceivingStore()
        }
      }
    }

    Provider.propTypes = {
        store: storeShape.isRequired,
        children: PropTypes.element.isRequired,
    }

    Provider.childContextTypes = {
        [storeKey]: storeShape.isRequired,
        [subscriptionKey]: subscriptionShape,
    }

    Provider.displayName = 'Provider'

    return Provider
}

我们看到,仅仅是把传入的store放在this[storeKey],又在getChildContext中把这个store返回给子组件而已。这里为了可扩展,storeKey当成参数(默认值store)传递进去。此外,子组件还能在context中获得另外一个值:key = storeSubscription ,值为null的属性。目前并没有什么卵用。

另外,每次改变store属性时候,都会报出一个警告。第二次改变时候不会警告,直接退出。也就是说Provider不支持动态修改store。

首先,在父组件中,定义两个属性:

**函数getChildContext,它定义了开放给子节点的context。

属性childContextTypes,它定义了getChildContext返回的对象类型。**
shoutingwei commented 6 years ago

Context

首先,在父组件中,定义两个属性:

函数getChildContext,它定义了开放给子节点的context。 属性childContextTypes,它定义了getChildContext返回的对象类型。

class ParentComponent extends React.Component {
  getChildContext() {
    return { foo: 'bar' }
  }

  render() {
    return <ChildComponent />
  }
}

ParentComponent.childContextTypes = {
  foo: React.PropTypes.string
}

ChildComponent 现在可以通过定义属性 contextTypes来获取context中的foo了: 在一个函数式,无状态的组件中,context通过第二个函数参数传入。在标准的用class定义的组件中,可以使用this.context定义context。

**const ChildComponent = (props, context) => {**
  return <p>The value of foo is: { context.foo }</p>
}
ChildComponent.contextTypes = {
  foo: React.PropTypes.string
}

如果你是开源库的作者,那么context是很有用的。比如说 React Router中的组件就使用了context。当你在写一个库的时候,组件之间如果需用通信或互传数据,context是完美的选择。另外一个使用了context的著名库是react-redux。

Router

const { Component, PropTypes } = React

class Router extends Component {
  getChildContext() {
//https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/Method_definitions
    const router = { register(url) { console.log('registered route!', url) } }//注意函数方式
    return { router: router }
  }
  render() { return <div>{this.props.children}</div> }
}
Router.childContextTypes = {
  router: PropTypes.object.isRequired,
}

Route

class Route extends Component {
  componentWillMount() {
    this.context.router.register(this.props.path)
  }
  render() {
    return <p>I am the route for {this.props.path}</p>
  }
}
Route.contextTypes = {
  router: PropTypes.object.isRequired,
}

示例

const App = () => (
  <div>
    <Router>
      <div>
        <Route path="/foo" />
        <Route path="/bar" />
        <div>
          <Route path="/baz" />
        </div>
      </div>
    </Router>
  </div>
)