Bootstrap

redux-thunk 源码解读

不用redux-thunk之前

// store.js
import { createStore } from 'redux';

export const reducer = (state = {
    count: 0
}, action) => {
    switch(action.type) {
        case 'CHANGE_DATA': {
            return {
                ...state,
                count: action.data
            }
        }
        default: 
            return state;

    }
}
export const store = createStore(reducer);

附:手写发布订阅

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script>
      //   const saleCenter = {};
      //   saleCenter.clientList = [];
      //   saleCenter.listen = function (fn) {
      //     this.clientList.push(fn);
      //   };
      //   saleCenter.attach = function () {
      //     for (let i = 0, fn; (fn = this.clientList[i++]); ) {
      //       fn.apply(this, arguments);
      //     }
      //   };

      //   // 测试
      //   saleCenter.listen((info) => {
      //       console.log(info)
      //   })
      //   saleCenter.listen((info) => {
      //       console.log(info);
      //   })
      //   saleCenter.attach('房子降价了')

      const saleCenter = {};
      saleCenter.clientList = {};
      saleCenter.listen = function (type, fn) {
        if (!this.clientList[type]) { // 如果还没有订阅过此类型的消息,则给该类型创建一个缓存列表
          this.clientList[type] = [];
        }
        this.clientList[type].push(fn);
      };
      saleCenter.attach = function () {
          const type = Array.prototype.shift.apply(arguments),
          fns = this.clientList[type]; // 取出该消息类型对应的回调函数集合
          if (!fns || fns.length === 0) {
              return false;
          }
        for (let i = 0, fn; (fn = fns[i++]); ) {
          fn.apply(this, arguments);
        }
      };

      // 测试
      saleCenter.listen('土豪', (info) => {
        console.log(info);
      });
      saleCenter.listen('土鳖', (info) => {
        console.log(info);
      });
      saleCenter.attach('土豪', '房子降价了');
    </script>
  </body>
</html>
// App.jsx
class ReduxTest extends React.Component {
    constructor(props) {
        super(props);
        store.subscribe(() => {
            console.log('subscribe')
            this.setState({
                count: store.getState().count
            })
        })
    }
    changeData = () => {
        const { count } = store.getState();
        const action = {
            type: 'CHANGE_DATA',
            data: count + 1
        }
        store.dispatch(action);
    }
    render() {
        return (
            <div>
                <span>{this.state?.count}</span>
                <button onClick={this.changeData}>按钮+1</button>
            </div>
        )
    }
}
export default ReduxTest;

对于上述代码,我们dispatch一个action,其中action必须为一个对象。

但是实际开发中,action里的数据往往是一个异步接口获取的数据,这个时候,我们可以

class ReduxTest extends React.Component {
    constructor(props) {
        super(props);
        store.subscribe(() => {
            console.log('subscribe', store.getState());
            this.setState({
                count: store.getState().count
            });
        });
    }
    changeData = () => {
        const { count } = store.getState();
        let res;
        const p = new Promise(resolve => {
            setTimeout(() => {
                res = 111;
                resolve(res);
            }, 1000);
        });
        p.then(r => {
            const action = {
                type: 'CHANGE_DATA',
                data: r
            };
            store.dispatch(action);
        });
    };
    render() {
        return (
            <div>
                <span>{this.state?.count}</span>
                <button onClick={this.changeData}>按钮+1</button>
            </div>
        );
    }
}
export default ReduxTest;

但是,上述会把处理的异步的逻辑写在组件里,使代码变得混乱,
因此,假如我dispatch一个函数,在这个函数里去处理异步的逻辑,岂不是使代码变得更简洁?!
但是根据Redux的三大原则之reducer必须为一个纯函数,所以不能直接dispatch一个函数,所以需要借用中间件redux-thunk。

const getData = () => {
    let res;
    const p = new Promise(resolve => {
        setTimeout(() => {
            res = 111;
            resolve(res);
        }, 1000);
    });
    p.then(r => {
      const action = {
          type: 'CHANGE_DATA',
          data: r
      };
      store.dispatch(action);
  });
};
class ReduxTest extends React.Component {
    constructor(props) {
        super(props);
        store.subscribe(() => {
            console.log('subscribe', store.getState());
            this.setState({
                count: store.getState().count
            });
        });
    }
    changeData = () => {
        const { count } = store.getState();
        store.dispatch(getData);
    };
    render() {
        return (
            <div>
                <span>{this.state?.count}</span>
                <button onClick={this.changeData}>按钮+1</button>
            </div>
        );
    }
}
export default ReduxTest;

这样,就将处理数据的逻辑放在了action层,而不是视图层,视图层只负责渲染就好。

总结:

其实没有redux-thunk,也可以完成同样的功能,只是将处理异步逻辑的代码写在组件里,为了让代码更简洁、解耦,所以通过redux-thunk可以dispatch一个函数,然后在这个函数里处理异步操作。
redux-thunk 源码参考:https://segmentfault.com/a/1190000022792225


2024-6-10日记
视频讲解:手写redux-thunk

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;