手写redux(一种思路)

初始项目

使用原生的provider和context

import React, { useState, useContext } from "react";
// 创建上下文
const appContext = React.createContext(null);
export const App = () => {
  const [appState, setAppState] = useState({
    user: { name: "frank", age: 18 },
  });
  // 初始化上下文
  const contextValue = { appState, setAppState };
  return (
    // 传递上下文
    <appContext.Provider value={contextValue}>
      <大儿子 />
      <二儿子 />
      <幺儿子 />
    </appContext.Provider>
  );
};
const 大儿子 = () => (
  <section>
    大儿子
    <User />
  </section>
);
const 二儿子 = () => (
  <section>
    二儿子
    <UserModifier />
  </section>
);
const 幺儿子 = () => <section>幺儿子</section>;
const User = () => {
  const contextValue = useContext(appContext);
  // console.log(contextValue);
  return <div>User:{contextValue.appState.user.name}</div>;
};
const UserModifier = () => {
  const contextValue = useContext(appContext);
  const { appState, setAppState } = contextValue;
  // const {appState, setAppState} = useContext(appContext)
  // console.log(contextValue)
  // console.log(appState)
  const onChange = (e) => {
    appState.user.name = e.target.value;
    // 修改上下文
    setAppState({ ...contextValue.appState });
  };
  return (
    <div>
      <input value={contextValue.appState.user.name} onChange={onChange} />
    </div>
  );
}; 

手写reducer

上文的代码中, 直接修改了state的原始内容, 不符合规范, 新增reducer来规范state创建流程

// 请从课程简介里下载本代码
import React, { useState, useContext } from "react";
// 创建上下文
const appContext = React.createContext(null);
export const App = () => {
  const [appState, setAppState] = useState({
    user: { name: "frank", age: 18 },
  });
  // 初始化上下文
  const contextValue = { appState, setAppState };
  return (
    // 传递上下文
    <appContext.Provider value={contextValue}>
      <大儿子 />
      <二儿子 />
      <幺儿子 />
    </appContext.Provider>
  );
};
const 大儿子 = () => (
  <section>
    大儿子
    <User />
  </section>
);
const 二儿子 = () => (
  <section>
    二儿子
    <UserModifier />
  </section>
);
const 幺儿子 = () => <section>幺儿子</section>;
const User = () => {
  const contextValue = useContext(appContext);
  // console.log(contextValue);
  return <div>User:{contextValue.appState.user.name}</div>;
};
// 规范state创建流程
const reducer = (state, { type, payload }) => {
  if (type === "updateUser") {
    return {
      ...state,
      user: {
        ...state.user,
        ...payload, // 其实payload就是data
      },
    };
  } else {
    return state;
  }
};
const UserModifier = () => {
  const contextValue = useContext(appContext);
  const { appState, setAppState } = contextValue;
  // const {appState, setAppState} = useContext(appContext)
  // console.log(contextValue)
  // console.log(appState)
  const onChange = (e) => {
    // appState.user.name = e.target.value;
    // 修改上下文
    // setAppState({ ...contextValue.appState });
    // 不是直接修改,而是创建新的state
    // 传入旧的state,创建新的
    setAppState(
      reducer(appState, {
        type: "updateUser",
        payload: {
          name: e.target.value,
        },
      })
    );
  };
  return (
    <div>
      <input value={contextValue.appState.user.name} onChange={onChange} />
    </div>
  );
}; 

手写dispatch

修改state需要setxxx, 如果多次, 则会在onChange函数里添加太多的set逻辑, 我们将他抽取出来, 作为一个function, 规范setState流程

// 请从课程简介里下载本代码
import React, { useState, useContext } from "react";
// 创建上下文
const appContext = React.createContext(null);
export const App = () => {
  const [appState, setAppState] = useState({
    user: { name: "frank", age: 18 },
  });
  // 初始化上下文
  const contextValue = { appState, setAppState };
  return (
    // 传递上下文
    <appContext.Provider value={contextValue}>
      <大儿子 />
      <二儿子 />
      <幺儿子 />
    </appContext.Provider>
  );
};
const 大儿子 = () => (
  <section>
    大儿子
    <User />
  </section>
);
const 二儿子 = () => (
  <section>
    二儿子
    {/* <UserModifier /> */}
    <Wrapper />
  </section>
);
const 幺儿子 = () => <section>幺儿子</section>;
const User = () => {
  const contextValue = useContext(appContext);
  // console.log(contextValue);
  return <div>User:{contextValue.appState.user.name}</div>;
};
// 规范state创建流程
const reducer = (state, { type, payload }) => {
  if (type === "updateUser") {
    return {
      ...state,
      user: {
        ...state.user,
        ...payload, // 其实payload就是data
      },
    };
  } else {
    return state;
  }
};
// context只能在组件内部访问,则这里创建一个组件来封装dispatch的逻辑,然后通过这个组件获取context(权宜之计)
const Wrapper = () => {
  const contextValue = useContext(appContext);
  const { appState, setAppState } = contextValue;
  // 规范setState流程
  const dispatch = (action) => {
    setAppState(reducer(appState, action));
  };
  return <UserModifier dispatch={dispatch} state={appState} />;
};
const UserModifier = ({ dispatch, state }) => { 
  const onChange = (e) => { 
    dispatch({
      type: "updateUser",
      payload: {
        name: e.target.value,
      },
    });
  };
  return (
    <div>
      <input value={state.user.name} onChange={onChange} />
    </div>
  );
};

connect 高阶组件

高阶组件: 一个函数,接收一个组件,返回一个新组件,这个组件就是高阶组件
connect: 将组件和全局的state联系起来

// 请从课程简介里下载本代码
import React, { useState, useContext } from "react";
// 创建上下文
const appContext = React.createContext(null);
export const App = () => {
  const [appState, setAppState] = useState({
    user: { name: "frank", age: 18 },
  });
  // 初始化上下文
  const contextValue = { appState, setAppState };
  return (
    // 传递上下文
    <appContext.Provider value={contextValue}>
      <大儿子 />
      <二儿子 />
      <幺儿子 />
    </appContext.Provider>
  );
};
const 大儿子 = () => (
  <section>
    大儿子
    <User />
  </section>
);
const 二儿子 = () => (
  <section>
    二儿子
    <UserModifier x={"x"}>内容</UserModifier>
    {/* <Wrapper /> */}
  </section>
);
const 幺儿子 = () => <section>幺儿子</section>;
const User = () => {
  const contextValue = useContext(appContext);
  // console.log(contextValue);
  return <div>User:{contextValue.appState.user.name}</div>;
};
// 规范state创建流程
const reducer = (state, { type, payload }) => {
  if (type === "updateUser") {
    return {
      ...state,
      user: {
        ...state.user,
        ...payload, // 其实payload就是data
      },
    };
  } else {
    return state;
  }
};
// 每次自己写warpper包装dispatch太麻烦,封装一个return wrapper的函数
const connent = (Component) => {
  // context只能在组件内部访问,则这里创建一个组件来封装dispatch的逻辑,然后通过这个组件获取context(权宜之计)
  return (props) => {
    const contextValue = useContext(appContext);
    const { appState, setAppState } = contextValue;
    // 规范setState流程
    const dispatch = (action) => {
      setAppState(reducer(appState, action));
    };
    // 如果组件有传值,应该传递(props)
    return <Component {...props} dispatch={dispatch} state={appState} />;
  };
}; 
// 传入组件,将组件和全局state联系起来,就是connect的含义
const UserModifier = connent(({ dispatch, state, ...props }) => {
  const { x, children } = props;
  console.log(x);
  const onChange = (e) => {
    dispatch({
      type: "updateUser",
      payload: {
        name: e.target.value,
      },
    });
  };
  return (
    <div>
      <div>{children}</div>
      <input value={state.user.name} onChange={onChange} />
    </div>
  );
});

实现精准render

当前每次都是重新渲染, 把没有改变的也更新了, 浪费性能(影响其实不大, 但是太多也不好)

1675999701929

// 请从课程简介里下载本代码
import { update } from "lodash";
import React, { useState, useContext, useMemo, useEffect } from "react";
// 创建上下文
const appContext = React.createContext(null);
// 方案2: 将组件的state提取到外部的store
const store = {
  state: {
    user: { name: "frank", age: 18 },
  },
  setState(newState) {
    // console.log(newState); 没有调用组件的setState,所以不会刷新
    store.state = newState;
    // 遍历监听者列表,调用用户传入的(更新)方法
    store.listeners.map((fn) => fn(store.state));
  },
  // 监听者
  listeners: [],
  // 让所有使用store的组件监听store的变化,实现更新渲染
  subscribe(fn) {
    store.listeners.push(fn);
    // 返回取消订阅的函数
    return () => {
      const index = store.listeners.indexOf(fn);
      store.listeners.splice(index, 1);
    };
  },
};
export const App = () => {
  // 只要改变了组件(app)的state,其返回的所有组件都会重新执行
  // useMemo(() => {
  //   return <幺儿子 />;
  // }, []); // 方案1: 使用缓存,传空数组表示以后不再更新
  // const [appState, setAppState] = useState({
  //   user: { name: "frank", age: 18 },
  // });
  // 初始化上下文
  // const contextValue = { appState, setAppState };
  return (
    // 传递上下文
    <appContext.Provider value={store}>
      <大儿子 />
      <二儿子 />
      <幺儿子 />
    </appContext.Provider>
  );
};
const 大儿子 = () => {
  console.log("大儿子");
  return (
    <section>
      大儿子
      <User />
    </section>
  );
};
const 二儿子 = () => {
  console.log("二儿子");
  return (
    <section>
      二儿子
      <UserModifier x={"x"}>内容</UserModifier>
      {/* <Wrapper /> */}
    </section>
  );
};
const 幺儿子 = () => {
  console.log("三儿子");
  return <section>幺儿子</section>;
};
// 每次自己写warpper包装dispatch太麻烦,封装一个return wrapper的函数
const connent = (Component) => {
  // context只能在组件内部访问,则这里创建一个组件来封装dispatch的逻辑,然后通过这个组件获取context(权宜之计)
  return (props) => {
    // 在wrapper使用state,调用更新方法,可以实现更新
    // 而且wrapper包装的是用到state的组件,所以更新时不会影响其他组件
    const [, update] = useState({});
    // const contextValue = useContext(appContext);
    const { state, setState } = useContext(appContext);
    // const { appState, setAppState } = contextValue;
    // 只订阅一次
    useEffect(() => {
      store.subscribe(() => {
        update({});
      });
    }, []);
    // 规范setState流程
    const dispatch = (action) => {
      // setAppState(reducer(appState, action));
      setState(reducer(state, action));
      // update({}); // 但是,其他使用store的无法根据store内的改变而重新渲染
    };
    // 如果组件有传值,应该传递(props)
    return <Component {...props} dispatch={dispatch} state={state} />;
  };
};
const User = connent(({ state, dispatch }) => {
  // const contextValue = useContext(appContext);
  // const { state } = useContext(appContext);
  // console.log(state);
  // console.log(contextValue);
  // return <div>User:{contextValue.appState.user.name}</div>;
  return <div>User:{state.user.name}</div>;
});
// 规范state创建流程
const reducer = (state, { type, payload }) => {
  if (type === "updateUser") {
    return {
      ...state,
      user: {
        ...state.user,
        ...payload, // 其实payload就是data
      },
    };
  } else {
    return state;
  }
}; 
// 传入组件,将组件和全局state联系起来,就是connect的含义
const UserModifier = connent(({ dispatch, state, ...props }) => {
  const { x, children } = props;
  console.log(x);
  const onChange = (e) => {
    dispatch({
      type: "updateUser",
      payload: {
        name: e.target.value,
      },
    });
  };
  return (
    <div>
      <div>{children}</div>
      <input value={state.user.name} onChange={onChange} />
    </div>
  );
}); 

1676000932952

redux乍现

App.jsx

// 请从课程简介里下载本代码
import React from "react";
// Uncaught SyntaxError: Unexpected token '<'
import { appContext, store, connent } from "./redux.jsx";
// 方案2: 将组件的state提取到外部的store
export const App = () => {
  // 只要改变了组件(app)的state,其返回的所有组件都会重新执行
  // useMemo(() => {
  //   return <幺儿子 />;
  // }, []); // 方案1: 使用缓存,传空数组表示以后不再更新
  // const [appState, setAppState] = useState({
  //   user: { name: "frank", age: 18 },
  // });
  // 初始化上下文
  // const contextValue = { appState, setAppState };
  return (
    // 传递上下文
    <appContext.Provider value={store}>
      <大儿子 />
      <二儿子 />
      <幺儿子 />
    </appContext.Provider>
  );
};
const 大儿子 = () => {
  console.log("大儿子");
  return (
    <section>
      大儿子
      <User />
    </section>
  );
};
const 二儿子 = () => {
  console.log("二儿子");
  return (
    <section>
      二儿子
      <UserModifier x={"x"}>内容</UserModifier>
      {/* <Wrapper /> */}
    </section>
  );
};
const 幺儿子 = () => {
  console.log("三儿子");
  return <section>幺儿子</section>;
};

const User = connent(({ state, dispatch }) => {
  // const contextValue = useContext(appContext);
  // const { state } = useContext(appContext);
  // console.log(state);
  // console.log(contextValue);
  // return <div>User:{contextValue.appState.user.name}</div>;
  return <div>User:{state.user.name}</div>;
}); 
// 传入组件,将组件和全局state联系起来,就是connect的含义
const UserModifier = connent(({ dispatch, state, ...props }) => {
  const { x, children } = props;
  console.log(x);
  const onChange = (e) => {
    dispatch({
      type: "updateUser",
      payload: {
        name: e.target.value,
      },
    });
  };
  return (
    <div>
      <div>{children}</div>
      <input value={state.user.name} onChange={onChange} />
    </div>
  );
});

redux.jsx

import React, { useState, useContext, useEffect } from "react";
// 每次自己写warpper包装dispatch太麻烦,封装一个return wrapper的函数
export const connent = (Component) => {
    // context只能在组件内部访问,则这里创建一个组件来封装dispatch的逻辑,然后通过这个组件获取context(权宜之计)
    return (props) => {
        // 在wrapper使用state,调用更新方法,可以实现更新
        // 而且wrapper包装的是用到state的组件,所以更新时不会影响其他组件
        const [, update] = useState({});
        // const contextValue = useContext(appContext);
        const { state, setState } = useContext(appContext);
        // const { appState, setAppState } = contextValue;
        // 只订阅一次
        useEffect(() => {
            store.subscribe(() => {
                update({});
            });
        }, []);
        // 规范setState流程
        const dispatch = (action) => {
            // setAppState(reducer(appState, action));
            setState(reducer(state, action));
            // update({}); // 但是,其他使用store的无法根据store内的改变而重新渲染
        };
        // 如果组件有传值,应该传递(props)
        return <Component {...props} dispatch={dispatch} state={state} />;
    };
};
// 规范state创建流程
const reducer = (state, { type, payload }) => {
    if (type === "updateUser") {
        return {
            ...state,
            user: {
                ...state.user,
                ...payload, // 其实payload就是data
            },
        };
    } else {
        return state;
    }
};
export const store = {
    state: {
        user: { name: "frank", age: 18 },
    },
    setState(newState) {
        // console.log(newState); 没有调用组件的setState,所以不会刷新
        store.state = newState;
        // 遍历监听者列表,调用用户传入的(更新)方法
        store.listeners.map((fn) => fn(store.state));
    },
    // 监听者
    listeners: [],
    // 让所有使用store的组件监听store的变化,实现更新渲染
    subscribe(fn) {
        store.listeners.push(fn);
        // 返回取消订阅的函数
        return () => {
            const index = store.listeners.indexOf(fn);
            store.listeners.splice(index, 1);
        };
    },
};
export const appContext = React.createContext(null)

让connect支持selector

App.jsx

// 请从课程简介里下载本代码
import React from "react";
// Uncaught SyntaxError: Unexpected token '<'
import { appContext, store, connent } from "./redux.jsx";
// 方案2: 将组件的state提取到外部的store
export const App = () => { 
  return (
    // 传递上下文
    <appContext.Provider value={store}>
      <大儿子 />
      <二儿子 />
      <幺儿子 />
    </appContext.Provider>
  );
}; 
// const x儿子 ......
const User = connent((state) => {
  // 获取局部state,无需再state.xxx.xxx
  return { user: state.user };
})(({ user }) => { 
  return <div>User:{user.name}</div>;
}); 
// 传入组件,将组件和全局state联系起来,就是connect的含义
const UserModifier = connent()(({ dispatch, state, ...props }) => {
  // ......
});

redux.jsx

import React, { useState, useContext, useEffect } from "react";
// 每次自己写warpper包装dispatch太麻烦,封装一个return wrapper的函数
// 这个写法表示 先接收一个参数,再接收一个参数
export const connent = (selector) => (Component) => {
  // context只能在组件内部访问,则这里创建一个组件来封装dispatch的逻辑,然后通过这个组件获取context(权宜之计)
  return (props) => {
    // 在wrapper使用state,调用更新方法,可以实现更新
    // 而且wrapper包装的是用到state的组件,所以更新时不会影响其他组件
    const [, update] = useState({});
    // const contextValue = useContext(appContext);
    const { state, setState } = useContext(appContext);
    // const { appState, setAppState } = contextValue;
    const data = selector ? selector(state) : { state: state };
    // 只订阅一次
    useEffect(() => {
      store.subscribe(() => {
        update({});
      });
    }, []);
    // 规范setState流程
    const dispatch = (action) => {
      // setAppState(reducer(appState, action));
      setState(reducer(state, action));
      // update({}); // 但是,其他使用store的无法根据store内的改变而重新渲染
    };
    // 如果组件有传值,应该传递(props)
    // return <Component {...props} dispatch={dispatch} state={state} />;
    return <Component {...props} dispatch={dispatch} {...data} />;
  };
};
// 规范state创建流程
const reducer = (state, { type, payload }) => {
  // ......
};
export const store = {
    // ......
};
export const appContext = React.createContext(null);

精准渲染

1676015232322

修改user后,三儿子也改变了

redux.jsx

import React, { useState, useContext, useEffect } from "react";
const changed = (oldState, newState) => {
  let changed = false;
  for (let key in oldState) {
    if (oldState[key] !== newState[key]) {
      changed = true;
    }
  }
  return changed;
};
// 每次自己写warpper包装dispatch太麻烦,封装一个return wrapper的函数
// 这个写法表示 先接收一个参数,再接收一个参数
export const connent = (selector) => (Component) => {
  // context只能在组件内部访问,则这里创建一个组件来封装dispatch的逻辑,然后通过这个组件获取context(权宜之计)
  return (props) => {
    // 在wrapper使用state,调用更新方法,可以实现更新
    // 而且wrapper包装的是用到state的组件,所以更新时不会影响其他组件
    const [, update] = useState({});
    // const contextValue = useContext(appContext);
    const { state, setState } = useContext(appContext);
    // const { appState, setAppState } = contextValue;
    const data = selector ? selector(state) : { state: state };
    // 只订阅一次
    useEffect(() => {
      //   const unsubscribe =
      return store.subscribe(() => {
        const newData = selector
          ? selector(store.state)
          : { state: store.state };
        // 判断data(store.state.xxx)是否发生变化
        if (changed(data, newData)) {
          console.log("update");
          update({});
        }
      });
      //   return unsubscribe; // 取消订阅
      // 这里最好取消订阅,否则selector变化时可能出现重复订阅
    }, [selector]);
    // 规范setState流程
    const dispatch = (action) => {
      // setAppState(reducer(appState, action));
      setState(reducer(state, action));
      // update({}); // 但是,其他使用store的无法根据store内的改变而重新渲染
    };
    // 如果组件有传值,应该传递(props)
    // return <Component {...props} dispatch={dispatch} state={state} />;
    return <Component {...props} dispatch={dispatch} {...data} />;
  };
};
// 规范state创建流程
const reducer = (state, { type, payload }) => {
  // ......
};
export const store = {
  // ......
};
export const appContext = React.createContext(null);

App.jsx

// 请从课程简介里下载本代码
import React from "react";
// Uncaught SyntaxError: Unexpected token '<'
import { appContext, store, connent } from "./redux.jsx";
// 方案2: 将组件的state提取到外部的store
export const App = () => { 
  return (
    // 传递上下文
    <appContext.Provider value={store}>
      <大儿子 />
      <二儿子 />
      <幺儿子 />
    </appContext.Provider>
  );
};
// ......
const 幺儿子 = connent((state) => { 
  return { group: state.group };
})(({ group }) => {
  console.log("三儿子");
  return (
    <section>
      幺儿子<div>Group: {group.name}</div>
    </section>
  );
});
const User = connent((state) => {
  // 获取局部state,无需再state.xxx.xxx
  return { user: state.user };
})(({ user }) => { 
  console.log('user')
  return <div>User:{user.name}</div>;
}); 
// 传入组件,将组件和全局state联系起来,就是connect的含义
const UserModifier = connent()(({ dispatch, state, ...props }) => {
  // ......
}); 

1676017417280

mapDispatcherToProps

1676017628946

App.jsx

// 请从课程简介里下载本代码
import React from "react";
// Uncaught SyntaxError: Unexpected token '<'
import { appContext, store, connent } from "./redux.jsx";
// 方案2: 将组件的state提取到外部的store
export const App = () => { 
  return (
    // 传递上下文
    <appContext.Provider value={store}>
      <大儿子 />
      <二儿子 />
      <幺儿子 />
    </appContext.Provider>
  );
};
// ......
// 传入组件,将组件和全局state联系起来,就是connect的含义
const UserModifier = connent(null, (dispatch) => {
  return {
    // 每次都要传update,简化
    updateUser: (attr) => dispatch({ type: "update", payload: attr }),
  };
  // })(({ dispatch, state, ...props }) => {
})(({ updateUser, state, ...props }) => {
  const { x, children } = props;
  console.log(x);
  const onChange = (e) => {
    updateUser({ name: e.target.value });
    // dispatch({
    //   type: "update",
    //   payload: {
    //     name: e.target.value,
    //   },
    // });
  };
  return (
    <div>
      <div>{children}</div>
      <input value={state.user.name} onChange={onChange} />
    </div>
  );
});  

redux.jsx

import React, { useState, useContext, useEffect } from "react";
const changed = (oldState, newState) => {
  // ......
};
// 每次自己写warpper包装dispatch太麻烦,封装一个return wrapper的函数
// 这个写法表示 先接收一个参数,再接收一个参数
// export const connent = (selector,mapDispatchToProps) => (Component) => {
export const connent = (selector, dispatchSelector) => (Component) => {
  // context只能在组件内部访问,则这里创建一个组件来封装dispatch的逻辑,然后通过这个组件获取context(权宜之计)
  return (props) => {
    // 在wrapper使用state,调用更新方法,可以实现更新
    // 而且wrapper包装的是用到state的组件,所以更新时不会影响其他组件
    const [, update] = useState({});
    // const contextValue = useContext(appContext);
    const { state, setState } = useContext(appContext);
    // const { appState, setAppState } = contextValue;
    const data = selector ? selector(state) : { state: state };
    // 规范setState流程
    const dispatch = (action) => {
      // setAppState(reducer(appState, action));
      setState(reducer(state, action));
      // update({}); // 但是,其他使用store的无法根据store内的改变而重新渲染
    };
    // 每次都要传update,简化
    const dispatchers = dispatchSelector
      ? dispatchSelector(dispatch)
      : { dispatch };
    // 只订阅一次
    useEffect(() => {
      //   const unsubscribe =
      return store.subscribe(() => {
        const newData = selector
          ? selector(store.state)
          : { state: store.state };
        // 判断data(store.state.xxx)是否发生变化
        if (changed(data, newData)) {
          console.log("update");
          update({});
        }
      }); 
      // return unsubscribe; // 取消订阅
      // 这里最好取消订阅,否则selector变化时可能出现重复订阅
    }, [selector]);
    // 如果组件有传值,应该传递(props)
    // return <Component {...props} dispatch={dispatch} state={state} />;
    // return <Component {...props} dispatch={dispatch} {...data} />;
    return <Component {...props} {...dispatchers} {...data} />;
  };
};
// 规范state创建流程
const reducer = (state, { type, payload }) => {
  // ......
};
export const store = {
    // ......
};
export const appContext = React.createContext(null);

connect的意义

多次调用, 每次调用产生半成品, 可以根据不同的半成品来进行读/写操作

1676026550867

connects/connectToUser.js

import { connect } from '../redux'
// 抽取公共selector和dispatcher
// 封装读逻辑
const userSelector = (state) => {
    return { user: state.user };
};
// 封装写逻辑
const userDispatcher = (dispatch) => {
    return {
        updateUser: (attrs) => dispatch({ type: "update", payload: attrs }),
    };
};
export const connentToUser = connect(userSelector, userDispatcher);

App.jsx

// 请从课程简介里下载本代码
import React from "react";
// Uncaught SyntaxError: Unexpected token '<'
import { appContext, store,connect } from "./redux.jsx";
import { connentToUser } from "./connects/connectToUser.js";
// 方案2: 将组件的state提取到外部的store
export const App = () => { 
  return (
    // 传递上下文
    <appContext.Provider value={store}>
      <大儿子 />
      <二儿子 />
      <幺儿子 />
    </appContext.Provider>
  );
};
// ......
const UserModifier = connentToUser(({ updateUser, user, ...props }) => {
  const { x, children } = props;
  console.log(x);
  const onChange = (e) => {
    updateUser({ name: e.target.value });
    // dispatch({
    //   type: "update",
    //   payload: {
    //     name: e.target.value,
    //   },
    // });
  };
  return (
    <div>
      <div>{children}</div>
      <input value={user.name} onChange={onChange} />
    </div>
  );
}); 

封装provider和createStore

1676029099571

redux.jsx

import React, { useState, useContext, useEffect } from "react";

// ......

// reduce和store都是写死的,不符合规范
// 规范state创建流程
// const reducer = (state, { type, payload }) => {
//   if (type === "update") {
//     return {
//       ...state,
//       user: {
//         ...state.user,
//         ...payload, // 其实payload就是data
//       },
//     };
//   } else {
//     return state;
//   }
// };
export const store = {
  // state: {
  // user: { name: "frank", age: 18 },
  // group: { name: "前端组" },
  // },
  state: undefined,
  setState(newState) {
    // console.log(newState); 没有调用组件的setState,所以不会刷新
    store.state = newState;
    // 遍历监听者列表,调用用户传入的(更新)方法
    store.listeners.map((fn) => fn(store.state));
  },
  reducer: undefined,
  // 监听者
  listeners: [],
  // 让所有使用store的组件监听store的变化,实现更新渲染
  subscribe(fn) {
    store.listeners.push(fn);
    // 返回取消订阅的函数
    return () => {
      const index = store.listeners.indexOf(fn);
      store.listeners.splice(index, 1);
    };
  },
};
// 创建store和reducer
export const createStore = (reducer, initState) => {
  store.state = initState;
  store.reducer = reducer;
  return store;
};
export const appContext = React.createContext(null);
export const Provider = ({ store, children }) => {
  return <appContext.Provider value={store}>{children}</appContext.Provider>;
};

App.jsx

// 请从课程简介里下载本代码
import React from "react";
// Uncaught SyntaxError: Unexpected token '<'
import { appContext, createStore, connect, Provider } from "./redux.jsx";
import { connentToUser } from "./connects/connectToUser.js";
const reducer = (state, { type, payload }) => {
  if (type === "update") {
    return {
      ...state,
      user: {
        ...state.user,
        ...payload,
      },
    };
  } else {
    return state;
  }
};
const initState = {
  user: { name: "frank", age: 23 },
  group: { name: "前端组" },
};
const store = createStore(reducer, initState);
// 方案2: 将组件的state提取到外部的store
export const App = () => {  
  return (
    // 传递上下文
    // <appContext.Provider value={store}>
    //   <大儿子 />
    //   <二儿子 />
    //   <幺儿子 />
    // </appContext.Provider>
    <Provider store={store}>
      <大儿子 />
      <二儿子 />
      <幺儿子 />
    </Provider>
  );
};
// ......
const User = connentToUser(({ user }) => {
  // ......
}); 
const UserModifier = connentToUser(({ updateUser, user, ...props }) => {
  // ......
});

redux概念总结

1676032605733

1676032698339

代码重构

redux.jsx

import React, { useState, useContext, useEffect } from "react";
const changed = (oldState, newState) => {
  let changed = false;
  for (let key in oldState) {
    if (oldState[key] !== newState[key]) {
      changed = true;
    }
  }
  return changed;
};
// 每次自己写warpper包装dispatch太麻烦,封装一个return wrapper的函数
// 这个写法表示 先接收一个参数,再接收一个参数
// export const connent = (selector,mapDispatchToProps) => (Component) => {
export const connect = (selector, dispatchSelector) => (Component) => {
  // context只能在组件内部访问,则这里创建一个组件来封装dispatch的逻辑,然后通过这个组件获取context(权宜之计)
  return (props) => {
    // 在wrapper使用state,调用更新方法,可以实现更新
    // 而且wrapper包装的是用到state的组件,所以更新时不会影响其他组件
    const [, update] = useState({});
    // const contextValue = useContext(appContext);
    const { setState } = useContext(appContext);
    // const { appState, setAppState } = contextValue;
    const data = selector ? selector(state) : { state: state };
    // // 规范setState流程
    // const dispatch = (action) => {
    //   // setAppState(reducer(appState, action));
    //   setState(reducer(state, action));
    //   // update({}); // 但是,其他使用store的无法根据store内的改变而重新渲染
    // };
    // 每次都要传update,简化
    const dispatchers = dispatchSelector
      ? dispatchSelector(dispatch)
      : { dispatch };
    // 只订阅一次
    useEffect(() => {
      //   const unsubscribe =
      return store.subscribe(() => {
        const newData = selector ? selector(state) : { state };
        // 判断data(store.state.xxx)是否发生变化
        if (changed(data, newData)) {
          console.log("update");
          update({});
        }
      });
      //   return unsubscribe; // 取消订阅
      // 这里最好取消订阅,否则selector变化时可能出现重复订阅
    }, [selector]);
    // 如果组件有传值,应该传递(props)
    // return <Component {...props} dispatch={dispatch} state={state} />;
    // return <Component {...props} dispatch={dispatch} {...data} />;
    return <Component {...props} {...dispatchers} {...data} />;
  };
}; 
let state = undefined;
let reducer = undefined;
// 监听者
let listeners = [];
const setState = (newState) => {
  // console.log(newState); 没有调用组件的setState,所以不会刷新
  state = newState;
  // 遍历监听者列表,调用用户传入的(更新)方法
  listeners.map((fn) => fn(state));
};
export const store = { 
  getState() {
    return state;
  },
  // 规范setState流程
  dispatch: (action) => {
    // setAppState(reducer(appState, action));
    setState(reducer(state, action));
    // update({}); // 但是,其他使用store的无法根据store内的改变而重新渲染
  },
  // 让所有使用store的组件监听store的变化,实现更新渲染
  subscribe(fn) {
    listeners.push(fn);
    // 返回取消订阅的函数
    return () => {
      const index = listeners.indexOf(fn);
      listeners.splice(index, 1);
    };
  }, 
};
const dispatch = store.dispatch;
// 创建store和reducer
export const createStore = (_reducer, initState) => {
  state = initState;
  reducer = _reducer;
  return store;
};
export const appContext = React.createContext(null);
export const Provider = ({ store, children }) => {
  return <appContext.Provider value={store}>{children}</appContext.Provider>;
};

redux如何支持异步


  转载请注明: malred-blog 手写redux(一种思路)

  目录