1. Public method files
1. connect file
import React, { useState } from "react"; import MyContext from "./MyContext"; import _ from "lodash"; // Simulate the connect high-order function of react-redux const connect = (mapStateToProps, mapDispatchToProps) => { return (Component) => props => wrapper(Component, {mapStateToProps, mapDispatchToProps, ...props}); }; const wrapper = (Comp, props) => { const {mapStateToProps, mapDispatchToProps, ...rest} = props return <MyContext.Consumer> {store => { const dispatch = _.get(store, 'dispatch'); const dispatches = mapDispatchToProps(dispatch); // store.subscribe listens to store.getState() to get the latest value const [states, setStates] = useState({}) store.subscribe(() => { const state1 = store.getState(); setStates(mapStateToProps(state1)) }); return <Comp {...{...states, ...dispatchs, ...rest}}/>; }} </MyContext.Consumer> } export default connect;
2. MyContext file:
import React from "react"; import createContext from './createContext' const MyContext = createContext({}); export default MyContext
3. createContext file:
import React from "react"; const createContext = ({}) => { let value = {}; const Provider = (props) => { value = props.value; return <>{props.children}</>; }; const Consumer = ({ children }: { children: any }) => { return <>{typeof children === "function" ? children(value) : children}</>; }; return { Provider, Consumer }; }; export default createContext;
4. reducer file
//Add list data and change array data // Split the business logic into a separate file to facilitate status management import _ from 'lodash'; export interface StateProps { id: number; text: string; isFinished: boolean; } export interface ActionProps { type: string; [key: string]: any; } interface IStateObjectProps { pickerArr: StateProps[]; filterTag: 'SHOW_ALL'|'SHOW_FINISHED'|'SHOW_NOT_FINISH'; dispatch: any; } const reducer = (state: IStateObjectProps, action: ActionProps) => { console.log(state, action, 'reducer'); const pickerArr0 = _.get(state, 'pickerArr')||[]; switch (action.type) { case "ADD": return { ...state, pickerArr: [...pickerArr0, _.get(action, 'todo')] }; case "CHANGESTATUS": const pickerArr = _.map(pickerArr0, (item) => { if (item.id === action.id) { return Object.assign({}, item, { isFinished: !_.get(item, 'isFinished') }); } return item; })||[]; return { ...state, pickerArr, } case 'SET_VISIBILITY_FILTER': const filterTag = action.filterTag; return { ...state, filterTag, }; default: return state || {}; } }; export default reducer
5. mapStateToProps file:
import React from "react"; import _ from "lodash"; import store from "./store"; // Different types of todo lists const getVisibleTodos = (todos, filter) => { switch (filter) { case "SHOW_ALL": // Show all return todos; case "SHOW_FINISHED": return todos.filter((t) => t.isFinished); case "SHOW_NOT_FINISH": return todos.filter((t) => !t.isFinished); default: return todos; } }; export const mapStateTotProps = (state) => { // console.log(state, 'mapStateTotProps', store) return { todoList: getVisibleTodos(_.get(state, 'pickerArr')||[], _.get(state, 'filterTag'))|| [], } }
6. mapDispatchToProps file
import React from "react"; import _ from "lodash"; import { StateProps } from "./reducer"; export const mapDispatchToProps = (dispatch) => { // console.log(dispatch, 'mapDispatchToProps============') // Filter todo list const onFilterTodoList = (filterTag) => { dispatch({ type: 'SET_VISIBILITY_FILTER', filterTag, }); }; const changeTodo = (id: number) => { dispatch({ type: "CHANGESTATUS", id: id }); }; // add todo const addTodo = (todo: StateProps) => { dispatch({ type: "ADD", todo }); }; const showAll = () => onFilterTodoList("SHOW_ALL"); const showFinished = () => onFilterTodoList("SHOW_FINISHED"); const showNotFinish = () => onFilterTodoList("SHOW_NOT_FINISH"); return { changeTodo, addTodo, showAll, showFinished, showNotFinish, }; }
It can be seen from the mapStateToProps file and mapDispatchToProps file that we need to find a way to obtain the latest state and a common dispatch method, which is the default export object in the store file mentioned below:
7. store files:
import React from 'react'; import reducer from './reducer' function createStore(reducer) { let state = null; const listeners = []; const subscribe = (fn) => listeners.push(fn); const getState = () => state; const dispatch = (action) => { const state1 = reducer(state, action); state = state1 // Because there is a monitoring callback executed after the latest state value is obtained, you can use store.subscribe to monitor the latest state value!!! listeners.forEach((fn) => fn()); return state } // dispatch({}) return { getState, dispatch, subscribe, reducer } } const store = createStore(reducer) console.log(store.getState(), 'oldState======') store.subscribe(() => { const newState = store.getState() //The data may change, you need to monitor the latest console.log(newState, 'newState===='); }) export default store;
8. ContextProvider component:
import React from "react"; import MyContext from "./MyContext"; import store from "./store"; import _ from "lodash"; // parent component const ContextProvider = ({ children }) => { return <MyContext.Provider value={store}>{children}</MyContext.Provider>; }; export default ContextProvider;
2. Using public files
1. TodoInput component
import React, { useState } from "react"; import "./TodoInput.scss"; import connect from './connect'; import { mapStateTotProps } from "./mapStateToProps"; import { mapDispatchToProps } from "./mapDispatchToProps"; // Subassembly const TodoInput = (props) => { const [text, setText] = useState(""); const { addTodo, showAll, showFinished, showNotFinish, } = props; const handleChangeText = (e: React.ChangeEvent) => { setText((e.target as HTMLInputElement).value); }; const handleAddTodo = () => { if (!text) return; addTodo({ id: new Date().getTime(), text: text, isFinished: false, }); setText(""); }; return ( <div className="todo-input"> <input type="text" placeholder="Please enter to-do items" onChange={handleChangeText} value={text} /> <button onClick={handleAddTodo}> + Add</button> <button onClick={showAll}>show all</button> <button onClick={showFinished}>show finished</button> <button onClick={showNotFinish}>show not finish</button> </div> ); }; export default connect(mapStateTotProps, mapDispatchToProps)(TodoInput);
2. TodoList component
import React from "react"; import TodoItem from "./TodoItem"; import _ from "lodash"; import connect from "./connect"; import { mapStateTotProps } from "./mapStateToProps"; import { mapDispatchToProps } from "./mapDispatchToProps"; const TodoList = (props) => { const { todoList } = props; return ( <> <p>checkbox-list:</p> <div className="todo-list"> {_.map(todoList, (item) => ( <TodoItem key={_.get(item, "id")} todo={item || {}} /> ))} </div> <hr /> </> ); }; export default connect(mapStateTotProps, mapDispatchToProps)(TodoList);
3. TodoItem component
import _ from 'lodash'; import React from "react"; import connect from './connect'; import { mapStateTotProps } from "./mapStateToProps"; import { mapDispatchToProps } from "./mapDispatchToProps"; // grandson component const TodoItem = (props: any) => { const { todo, changeTodo } = props; //Change event status const handleChange = () => { changeTodo(_.get(todo, 'id')); } return ( <div className="todo-item"> <input type="checkbox" checked={todo.isFinished} onChange={handleChange} /> <span style={<!-- -->{ textDecoration: _.get(todo, 'isFinished') ? 'line-through' : 'none' }}>{todo.text}</span> </div> ) } export default connect(mapStateTotProps, mapDispatchToProps)(TodoItem);
4. Todo component:
import React from "react"; import TodoInput from "./TodoInput"; import TodoList from "./TodoList"; // parent component const Todo = () => { return ( <> <TodoInput /> <TodoList /> </> ); }; export default Todo;
5. The App component uses ContextProvider to wrap the Todo component
import React from "react"; import Todo from './mockConnectProvider/Todo' import ContextProvider from './mockConnectProvider/ContextProvider' const App: React.FC = () => { return ( <ContextProvider> <Todo /> </ContextProvider> ); }; export default App;
The rendering is as follows:
3. Use recoil to simulate connect
1. App.tsx file
import React from "react"; import Todo from "./mockConnectProvider/Todo"; import { RecoilRoot } from "recoil"; const App: React.FC = () => { return ( <RecoilRoot> <Todo /> </RecoilRoot> ); }; export default App;
2. Todo.tsx
import React from "react"; import TodoInput from "./TodoInput"; import TodoList from "./TodoList"; import ContextProvider from "./ContextProvider"; // parent component const Todo = () => { return ( <ContextProvider> <TodoInput /> <TodoList /> </ContextProvider> ); }; export default Todo;
3. connect.tsx
import React from "react"; import MyContext from "./MyContext"; import _ from "lodash"; import { atom, useRecoilState } from "recoil"; const connectState1 = atom({ key: "connect-state1", default: {}, }); // Simulate the connect high-order function of react-redux const connect = (mapStateToProps, mapDispatchToProps) => { return (Component) => (props) => wrapper(Component, { mapStateToProps, mapDispatchToProps, ...props }); }; const useReducer = (store) => { const [state, setState] = useRecoilState(connectState1); const dispatch = (action) => { const state1 = store.dispatch(action); setState((state0: any) => ({ ...state0, ...state1, })); }; return [state, dispatch]; }; const wrapper = (Comp, props) => { const { mapStateToProps, mapDispatchToProps, ...rest } = props; return ( <MyContext.Consumer> {(store) => { const [state, dispatch] = useReducer(store); const dispatches = mapDispatchToProps(dispatch); let states1 = mapStateToProps(state); return <Comp {...{ ...states1, ...dispatchs, ...rest }} />; }} </MyContext.Consumer> ); }; export default connect;
For other files, just take the first and second files
Principle:
1) The RecoilRoot tag is similar to the Provider tag of redux. With it, it facilitates global component communication.
2) The useRecoilState hook can set global variables under the same source. Naturally, if one modification is made, all components can trigger updates.
4. Context simulates connect
1. App.tsx
import React from "react"; import Todo from "./mockConnectProvider/Todo"; const App: React.FC = () => { return ( <> <Todo /> </> ); }; export default App;
2. Todo.tsx
import React from "react"; import TodoInput from "./TodoInput"; import TodoList from "./TodoList"; import ContextProvider from "./ContextProvider"; // parent component const Todo = () => { return ( <ContextProvider> <TodoInput /> <TodoList /> </ContextProvider> ); }; export default Todo;
3. MyContext file
import React from "react"; const MyContext = React.createContext({}); export default MyContext
4. ContextProvider.tsx
import React, { useContext } from "react"; import MyContext from "./MyContext"; import _ from "lodash"; import reducer from "./reducer"; // parent component const ContextProvider = ({ children }) => { const context:any = useContext(MyContext); const [state, dispatch] = React.useReducer(reducer, context); return <MyContext.Provider value={<!-- -->{getState:() => state, dispatch}}>{children}</MyContext.Provider>; }; export default ContextProvider;
5. connect.tsx
import React from "react"; import MyContext from "./MyContext"; import _ from "lodash"; // Simulate the connect high-order function of react-redux const connect = (mapStateToProps, mapDispatchToProps) => { return (Component) => (props) => wrapper(Component, { mapStateToProps, mapDispatchToProps, ...props }); }; const wrapper = (Comp, props) => { const { mapStateToProps, mapDispatchToProps, ...rest } = props; return ( <MyContext.Consumer> {(store) => { const dispatches = mapDispatchToProps(_.get(store, 'dispatch')); let states1 = mapStateToProps(_.get(store, 'getState') ? _.get(store, 'getState')(): {}); return <Comp {...{ ...states1, ...dispatchs, ...rest }} />; }} </MyContext.Consumer> ); }; export default connect;
6. Find other required component files from 1 and 2,
Note: This method does not require a store file.