美文网首页
React组件通信的几种方式--TypeScript

React组件通信的几种方式--TypeScript

作者: 北风吹_yfy | 来源:发表于2021-11-18 15:29 被阅读0次

    React组件通信的几种方式--TypeScript

    通过todoList示例进行学习,首先创建基于TypeScript的react工程:
    npx create-react-app myapp --template typescript
    示例:

    image.png

    一、props&Event

    • 父组件向子组件通信:
      父组件通过向子组件传递 props,子组件得到 props 后进行相应的处理。
    • 子组件向父组件通信:
      利用回调函数,父组件将一个函数作为 props 传递给子组件,子组件调用该回调函数,便可以向父组件通信。
    • 跨级组件通信:
      跨级组件通信,就是父组件向子组件的子组件通信,向更深层的子组件通信,解决方法就是中间组件层层传递 props。
      如果父组件嵌套较深,那么中间的每一层组件都要去传递 props,增加了复杂度,也给后期维护带来不便,并且这些 props 并不是这些中间组件自己所需要的。

    父组件传递props:

    // 父组件
    const Todo = () => {
        // 把数据统一存到todo父组件里
        const [todoList, setTodoList] = useState<StateProps[]>([]);
        // 改变todo
        const changeTodo = (id: number) => {
            const newTodoList = todoList.map(item => {
                if (item.id === id) {
                    return Object.assign({}, item, { isFinished: !item.isFinished })
                }
                return item;
            })
            setTodoList(newTodoList);
        }
        // 添加todo
        const addTodo = (todo: StateProps) => {
            setTodoList([...todoList, todo]);
        }
        return (
            <div className="todo">
                <TodoInput addTodo={addTodo} />
                <TodoList todoList={todoList} changeTodo={changeTodo} />
            </div>
        )
    }
    

    子组件接收 props 与调用回到函数:

    // 子组件
    const TodoInput = ({ addTodo }: IProps) => {
        const [text, setText] = useState('');
        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="请输入代办事项" onChange={handleChangeText} value={text} />
                <button style={{ marginLeft: '10px' }} onClick={handleAddTodo} >+添加</button>
            </div>
        )
    }
    
    const TodoList = ({ todoList, changeTodo }: IProps) => {
        return (
            <div className="todo-list" style={style}>
                {todoList.map(item => <TodoItem key={item.id} todo={item} changeTodo={changeTodo} />)}
            </div>
        )
    }
    
    // 孙子组件
    const TodoItem = ({ todo, changeTodo }: IProps) => {
        // 改变事项状态
        const handleChange = () => {
            changeTodo(todo.id);
        }
        return (
            <div className="todo-item" style={style}>
                <input type="checkbox" checked={todo.isFinished} onChange={handleChange} />
                <span style={{ textDecoration: todo.isFinished ? 'line-through' : 'none' }}>{todo.text}</span>
            </div>
        )
    }
    

    二、Context

    context 相当于一个全局变量,是一个大容器,我们可以把要通信的内容放在这个容器中,这样一来,不管嵌套有多深,都可以随意取用。
    使用 context 也很简单,需要满足两个条件:

    • 上级组件要声明自己支持 context,并提供一个函数来返回相应的 context 对象;
    • 子组件要声明自己需要使用 context。
      使用步骤:
      (1)通过 createContext 创建一个 context 对象,然后给这个对象指定属性值;
      (2)通过用 <MyContext.Provider> </MyContext.Provider> 组件包裹需要接收上层属性的子组件;
      (3)将需要传递的属性放在 Provider 组件的 value 属性上向下传递;
      (4)在子组件中获取值时使用 useContext 来简化操作。
    // Provide组件
    import React, { createContext, useState } from "react";
    export interface StateProps {
        id: number;
        text: string;
        isFinished: boolean;
    }
    export interface ContextProps {
        todoList: StateProps[];
        changeTodo: (id: number) => void;
        addTodo: (todo: StateProps) => void;
    }
    // const MyContext = createContext<ContextProps | null>(null); // 泛型写法
    export const MyContext = createContext({} as ContextProps); // 断言写法
    const MyProvide = (props: React.PropsWithChildren<{}>) => {
        // 把数据统一存到todo父组件里
        const [todoList, setTodoList] = useState<StateProps[]>([]);
        // 改变todo
        const changeTodo = (id: number) => {
            const newTodoList = todoList.map(item => {
                if (item.id === id) {
                    return Object.assign({}, item, { isFinished: !item.isFinished })
                }
                return item;
            })
            setTodoList(newTodoList);
        }
        // 添加todo
        const addTodo = (todo: StateProps) => {
            setTodoList([...todoList, todo]);
        }
        return (
            <MyContext.Provider value={{ todoList, changeTodo, addTodo }} >
                {/* 插槽内容 */}
                {props.children}
            </MyContext.Provider>
        )
    }
    export default MyProvide;
    
    // 子组件
    import React, { useContext } from 'react';
    import TodoItem from './TodoItem';
    import { MyContext } from './MyProvider';
    const style = {
        marginTop: '20px',
    }
    const TodoList = () => {
        const { todoList } = useContext(MyContext);
        return (
            <div className="todo-list" style={style}>
                {todoList.map(item => <TodoItem key={item.id} todo={item} />)}
            </div>
        )
    }
    

    以上方法中的todoList是一个相对复杂的数据,使用useState定义不太好维护,所以还可以使用 useReducer 进行优化。

    // 将业务逻辑拆分到一个单独文件中,方便进行状态管理
    export interface StateProps {
        id: number;
        text: string;
        isFinished: boolean;
    }
    export interface ActionProps {
        type: string;
        [key: string]: any;
    }
    const reducer = (state: StateProps[], action: ActionProps) => {
        switch (action.type) {
            case 'ADD':
                return [...state, action.todo];
            case 'CHANGESTATUS':
                return state.map(item => {
                    if (item.id === action.id) {
                        return Object.assign({}, item, { isFinished: !item.isFinished })
                    }
                    return item;
                });
            default:
                return state;
        }
    }
    export default reducer;
    
    // Provider 组件
    import React, { createContext, useReducer } from "react";
    import reducer, { StateProps, ActionProps } from "../store/reducer";
    export interface ContextProps {
        state: StateProps[];
        dispatch: React.Dispatch<ActionProps>;
    }
    // const MyContext = createContext<ContextProps | null>(null); // 泛型写法
    export const MyContext = createContext({} as ContextProps); // 断言写法
    const MyProvide = (props: React.PropsWithChildren<{}>) => {
        // 把数据统一存到todo父组件里
        // const [todoList, setTodoList] = useState<StateProps[]>([]);
        // 改用useReducer进行优化
        const initState: StateProps[] = [];
        const [state, dispatch] = useReducer(reducer, initState)
        return (
            <MyContext.Provider value={{ state, dispatch }} >
                {/* 插槽内容 */}
                {props.children}
            </MyContext.Provider>
        )
    }
    

    需要注意 dispatch 中的 type 值需要与 reducer 中定义的一一对应。

    三、Redux

    先安装相关依赖:
    npm i -S redux react-redux @types/react-redux

    Redux基本概念
    • Store (状态容器)
      Store 就是保存数据的地方,整个应用只能有一个 Store。
    • State
      Store对象包含所有数据。当前时刻的 State,可以通过store.getState()拿到。Redux 规定, 一个 State 对应一个 View。
    • reducer
      是一个纯函数(什么样的输入就有什么样的输出),它接受 Action 和当前 State 作为参数,返回一个新的 State,不会进行 DOM 操作和 Ajax 请求,不能调用Date.now()或者Math.random()等不纯的方法,因为每次会得到不一样的结果。
    • action
      Action 就是 View 发出的通知,表示 State 应该要发生变化了。
      Action 是一个对象。其中的type属性是必须的,表示 Action 的名称,其他属性可以自由设置。
    • dispatch
      dispatch 是 View 发出 Action 的唯一方法。
    使用步骤:

    1、通过 createStore 实例化 store 实例:

    import { createStore } from "redux";
    import reducer from "./reducer";
    const store = createStore(reducer, []);
    

    2、在根组件引入 Provider 组件,将其作为最外侧,并传入 store 实例,这样所有子组件都能拿到 store 里的状态:

    import { Provider } from 'react-redux';
    import store from '../store';
    // 父组件
    const Todo = () => {
        return (
            <Provider store={store}>
                <div className="todo">
                    <TodoInput />
                    <TodoList />
                </div>
            </Provider>
        )
    }
    

    3、在 reducer 函数中定义状态和状态处理,其实就是根据不同的 action 对象分别对状态做不同的处理:

    const reducer = (state: StateProps[] = [], action: ActionProps) => {
        switch (action.type) {
            case types.ADD:
                return [...state, action.todo];
            case types.CHANGESTATUS:
                return state.map(item => {
                    if (item.id === action.id) {
                        return Object.assign({}, item, { isFinished: !item.isFinished })
                    }
                    return item;
                });
            default:
                return state;
        }
    }
    

    4、定义 action 对象:

    import * as types from './actionType';
    import { StateProps } from './reducer';
    export const addAction = (todo: StateProps) => ({
        type: types.ADD,
        todo,
    })
    export const changeAction = (id: number) => ({
        type: types.CHANGESTATUS,
        id,
    })
    

    5、当在某一个事件发生的时候,通过调用 dispatch 传入 action 对象,从而实现 state 的改变,这里引入 hooks 的 useDispatch 方法简化代码:

    import { useDispatch } from 'react-redux'; // 减少代码复杂度
    import { addAction } from '../store/action';
    
    const dispatch = useDispatch();
    
    dispatch(addAction({
                id: new Date().getTime(),
                text: text,
                isFinished: false,
            }))
    

    6、使用 useSelector 获取状态值:

    import { useSelector } from 'react-redux';
    import { RootState } from '../store/reducer';
    
    const state = useSelector((state: RootState) => state);  // 返回具体要拿的store中的state或者整个store
    

    四、Mobx

    先安装相关依赖:
    npm i mobx mobx-react

    使用步骤

    1、定义一个状态类,在类中定义状态以及修改状态的方法,并调用 mobx 中的 makeAutoObservable 方法将状态和方法转换为可观察的:

    import { makeAutoObservable } from 'mobx';
    
    class TodoStore {
        todoList: StateProps[] = [];
        constructor() {
            makeAutoObservable(this); // 自动转换
        }
        // 添加事项
        addAction(todo: StateProps) {
            this.todoList.push(todo)
        }
        // 改变事项状态
        changeAction(id: number) {
            this.todoList = this.todoList.map(item => {
                if (item.id === id) {
                    return Object.assign({}, item, {
                        isFinished: !item.isFinished
                    })
                }
                return item;
            })
        }
    }
    

    2、配合 react 的 createContext 将数据注入子组件:

    import React, { createContext, useContext } from 'react';
    import todoStore from './TodoStore';
    export const MyContext = createContext<typeof todoStore>(todoStore);
    
    const MyProvider: React.FC = ({ children }) => {
        return (
            <MyContext.Provider value={todoStore}>
                {children}
            </MyContext.Provider>
        )
    }
    

    3、自定义一个 hooks 用来获取 store 数据:

    // 自定义 hook 获取 store
    export const useStore = () => {
        const store = useContext(MyContext);
        if (!store) throw Error('no store');
        return store;
    }
    

    4、通过 mobx 的 observer 包裹需要引用 store 数据的组件,将其变为响应式组件:

    import React from 'react';
    import { observer } from 'mobx-react';
    import TodoItem from './TodoItem';
    import { useStore } from '../store/index';
    // 子组件
    const TodoList = () => {
        const store = useStore();
        return (
            <div className="todo-list">
                {store.todoList.map(item => <TodoItem key={item.id} todo={item} />)}
            </div>
        )
    }
    export default observer(TodoList);
    

    用 useRef 和 useEffct 对 todoList 做优化:

    import React, { useRef, useEffect } from 'react';
    import { useStore } from '../store/index';
    import { observer } from 'mobx-react';
    const TodoInput = () => {
        // const [text, setText] = useState('');
        const inputRef = useRef<HTMLInputElement>(null);
        const store = useStore();
        // const handleChangeText = (e: React.ChangeEvent) => {
        //     setText((e.target as HTMLInputElement).value);
        // }
        // 相当于 componentDidMount、componentDidUpdate、componentWillUnmount 的集合
        useEffect(() => {
            inputRef?.current!.focus();
        }, [])
        const handleAddTodo = () => {
            const value = inputRef.current!.value;
            store.addAction({
                id: new Date().getTime(),
                text: value,
                isFinished: false,
            })
            inputRef.current!.value = '';
        }
        return (
            <div className="todo-input">
                {/* <input type="text" placeholder="请输入代办事项" onChange={handleChangeText} value={text} /> */}
                <input type="text" placeholder="请输入代办事项" ref={inputRef} />
                <button style={{ marginLeft: '10px' }} onClick={handleAddTodo} >+添加</button>
            </div>
        )
    }
    export default observer(TodoInput);
    

    五、总结

    1、props&Event 针对组件嵌套层级比较浅;
    2、Context 结合 Hooks 也是比较简单的,适合解决一些小型数据管理;
    3、Mobx 和 Redux 状态管理库适合管理大型数据。

    相关文章

      网友评论

          本文标题:React组件通信的几种方式--TypeScript

          本文链接:https://www.haomeiwen.com/subject/osodtrtx.html