react中useContext及createContext的用法

91 阅读4分钟

1.先通过一个简单案例说一下二者的写法

import { useState , useContext, createContext } from 'react'
import './App.css'

const countContext = createContext(0);
function App() {
  const [count, setCount] = useState(0)

  return (
    <div className="App">
     <button onClick={() => setCount(count + 2)}>按钮值是:{count}</button>
     <countContext.Provider value={count}>
        <Son />
        <Children />
     </countContext.Provider>
     
    </div>
  )
}

const Son = () => {
  const Num = useContext(countContext);
  return <h2>第一个子接收值是:{Num}</h2>
}


const Children = () => {
   return (
     <countContext.Consumer>
       {
         n => <h2>第二个组件接收到的值是:{n}</h2>
       }
     </countContext.Consumer>
   )
}

export default App

效果图:

image.png

2.使用react 官网标题的例子: Heading.jsx

import { useContext } from 'react';
import { LevelContext } from './LevelContext.js';

export default function Heading({ children }) {
  const level = useContext(LevelContext);
  switch (level) {
    case 0:
      throw Error('Heading 必须在 Section 内部!');
    case 1:
      return <h1>{children}</h1>;
    case 2:
      return <h2>{children}</h2>;
    case 3:
      return <h3>{children}</h3>;
    case 4:
      return <h4>{children}</h4>;
    case 5:
      return <h5>{children}</h5>;
    case 6:
      return <h6>{children}</h6>;
    default:
      throw Error('未知的 level:' + level);
  }
}

Section.jsx

import {useContext} from 'react';
import {LevelContext} from './LevelContext';

export default function Section ({children}) {
    const level = useContext(LevelContext);
    return (
        <section>
            <LevelContext.Provider value={level + 1}>
               {children}
            </LevelContext.Provider>
        </section>
    )
}

LevelContext.js

import {createContext} from 'react';

export const LevelContext = createContext(1);

新建Title.jsx

import Heading from './Heading';
import Section from './Section';

export default function Title() {
  return (
    <Section>
      <Heading>主标题</Heading>
      <Section>
        <Heading>副标题</Heading>
        <Heading>副标题</Heading>
        <Heading>副标题</Heading>
        <Section>
          <Heading>子标题</Heading>
          <Heading>子标题</Heading>
          <Heading>子标题</Heading>
          <Section>
            <Heading>子子标题</Heading>
            <Heading>子子标题</Heading>
            <Heading>子子标题</Heading>
          </Section>
        </Section>
      </Section>
    </Section>
  );
}

效果如下:

image.png

3.## Context 会穿过中间层级的组件 App.js

import Heading from './Heading';
import Section from './Section';

export default function Title() {
  return (
    <Section>
     <Heading>My Profile</Heading>
     <Post
        title="旅行者,你好!"
        body="来看看我的冒险。"
      />
     <AllPosts />
    </Section>
  );
}
function AllPosts () {
    return (
        <Section>
            <Heading>帖子</Heading>
            <RecentPosts />
        </Section>
    )
}

function RecentPosts () {
    return (
        <Section>
            <Heading>最近的帖子</Heading>
            <Post 
               title="里斯本的味道"
               body="...那些蛋挞!"
            />
            <Post
               title="探戈节奏中的布宜诺斯艾利斯"
               body="我爱它!"
            />
        </Section>
    )
}

function Post ({title, body}) {
    return (
        <Section isFancy={true}>
           <Heading>
             {title}
           </Heading>
           <p><i>{body}</i></p>
        </Section>
    )

}

Heading.jsx

import { useContext } from 'react';
import { LevelContext } from './LevelContext.js';

export default function Heading({ children }) {
  const level = useContext(LevelContext);
  switch (level) {
    case 0:
      throw Error('Heading 必须在 Section 内部!');
    case 1:
      return <h1>{children}</h1>;
    case 2:
      return <h2>{children}</h2>;
    case 3:
      return <h3>{children}</h3>;
    case 4:
      return <h4>{children}</h4>;
    case 5:
      return <h5>{children}</h5>;
    case 6:
      return <h6>{children}</h6>;
    default:
      throw Error('未知的 level:' + level);
  }
}

Section.jsx

import {useContext} from 'react';
import {LevelContext} from './LevelContext';

export default function Section ({children, isFancy}) {
    const level = useContext(LevelContext);
    return (
        <section className={
            'section' + 
            (isFancy ? 'fancy' : '')
        }>
            <LevelContext.Provider value={level + 1}>
               {children}
            </LevelContext.Provider>
        </section>
    )
}

LevelContext.js

import {createContext} from 'react';

export const LevelContext = createContext(0);

4.## 结合使用 reducer 和 context TaskApp.jsx

import {useReducer} from 'react';
import AddTask from './AddTask.jsx';
import TaskList from './TaskList.jsx';

export default function TaskApp() {
    const [tasks, dispatch] = useReducer(
        tasksReducer,
        initialTasks
    );
    function handleAddTask(text){
        dispatch({
            type: 'added',
            id: nextId++,
            text: text,
        })
    };
    function handleChangeTask(task){
        dispatch({
            type: 'changed',
            task: task,
        })
    };
    function handleDeleteTask(taskId){
        dispatch({
            type: 'deleted',
            id: taskId,
        })
    };

    return (
        <>
            <h1>Day off in Kyoto</h1>
            <AddTask onAddTask={handleAddTask} />
            <TaskList 
                tasks={tasks}
                onChangeTask={handleChangeTask}
                onDeleteTask={handleDeleteTask}
            /> 
        </>
    )
}

function tasksReducer(tasks, action) {
    switch (action.type) {
        case 'added': {
            return [...tasks,{
                id: action.id,
                text: action.text,
                done: false
            }];
        }
        case 'changed': {
            return tasks.map(t => {
                if(t.id === action.task.id){
                    return action.task;
                }else {
                    return t;
                }
            })
        }

        case 'deleted': {
            return tasks.filter(t => t.id !== action.id);
        }

        default : {
            throw Error('Unknown action: ' + action.type);
        }   
    }
}

let nextId = 3;
const initialTasks = [
    { id: 0, text: 'Philosopher’s Path', done: true },
    { id: 1, text: 'Visit the temple', done: false },
    { id: 2, text: 'Drink matcha', done: false }
];

AddTask.jsx

import {useState} from 'react'

export default function AddTask ({onAddTask}) {
    const [text, setText] = useState('');
    return (
        <>
           <input 
              placeholder='Add Task'
              value={text}
              onChange={e => setText(e.target.value)}
            />
            <button 
                onClick={()=>{
                    setText('');
                    onAddTask(text);
                }}
            >
                Add
            </button>
        </>
    )
}

TaskList.jsx

import {useState} from 'react';

export default function TaskList ({tasks, onChangeTask, onDeleteTask}) {
    return (
        <ul>
            {
                tasks.map(task => (
                    <li key={task.id}>
                       <Task 
                          task={task}
                          onChange={onChangeTask}
                          onDelete={onDeleteTask}
                       />
                    </li>
                ))
            }
        </ul>
    )
}

function Task ({task, onChange, onDelete}) {
    const [isEditing, setIsEditing] = useState(false);
    let taskContent;
    if(isEditing) {
        taskContent = (
            <>
                <input 
                    value={task.text}
                    onChange={e=>{
                        onChange({
                            ...task,
                            text: e.target.value
                        })
                    }}
                />
                <button onClick={()=> setIsEditing(false)}> Save</button>
            </>
        )
    } else {
        taskContent = (
            <>
                {task.text}
                <button onClick={() => setIsEditing(true) }>
                    Edit
                </button>

            </>
        )
    }

    return (
        <label>
            <input 
                type='checkbox'
                checked={task.done}
                onChange={e=> {
                    onChange({
                        ...task,
                        done: e.target.checked
                    })
                }}
            />
            {taskContent}
            <button onClick={()=> onDelete(task.id)}>Delete</button>
        </label>
    )
}

效果如下:

image.png

5.使用 reducer 和 context修改上述例子

(1)### 将 state 和 dispatch 函数 放入 context 新建TasksContext.jsx

import {createContext} from 'react';

export const TasksContext = createContext(null);
export const TaskDispatchContext = createContext(null);

TaskApp.jsx

import {TasksContext, TaskDispatchContext} from './TasksContext';

 <TasksContext.Provider value={tasks}>
      <TaskDispatchContext.Provider value={dispatch}>
      <h1>Day off in Kyoto</h1>
      <AddTask onAddTask={handleAddTask} />
      <TaskList 
          tasks={tasks}
          onChangeTask={handleChangeTask}
          onDeleteTask={handleDeleteTask}
      /> 
      </TaskDispatchContext.Provider>
  </TasksContext.Provider>

(2)### 在组件树中的任何地方使用 context

TaskApp.jsx

import {useState, useContext} from 'react';
import {TaskDispatchContext} from './TasksContext';

export default function AddTask () {
    const [text, setText] = useState('');
    const diapatch = useContext(TaskDispatchContext);
    return (
        <>
           <input 
              placeholder='Add Task'
              value={text}
              onChange={e => setText(e.target.value)}
            />
            <button 
                onClick={()=>{
                    setText('');
                    diapatch({
                        type: 'added',
                        id: nextId++,
                        text: text,
                    })
                }}
            >
                Add
            </button>
        </>
    )
}
let nextId = 3;

TasksContext.js

import {createContext} from 'react';

export const TasksContext = createContext(null);
export const TaskDispatchContext = createContext(null);

TaskList.jsx

import {useState, useContext} from 'react';
import {TasksContext, TaskDispatchContext} from './TasksContext';

export default function TaskList () {
    const tasks = useContext(TasksContext);
    return (
        <ul>
            {
                tasks.map(task => (
                    <li key={task.id}>
                       <Task 
                          task={task}
                       />
                    </li>
                ))
            }
        </ul>
    )
}

function Task ({task}) {
    const [isEditing, setIsEditing] = useState(false);
    const diapatch = useContext(TaskDispatchContext);
    let taskContent;
    if(isEditing) {
        taskContent = (
            <>
                <input 
                    value={task.text}
                    onChange={e=>{
                        diapatch({
                            type: 'changed',
                            task: {
                                ...task,
                                text: e.target.value,
                            }
                        })
                    }}
                />
                <button onClick={()=> setIsEditing(false)}> Save</button>
            </>
        )
    } else {
        taskContent = (
            <>
                {task.text}
                <button onClick={() => setIsEditing(true) }>
                    Edit
                </button>

            </>
        )
    }

    return (
        <label>
            <input 
                type='checkbox'
                checked={task.done}
                onChange={e=> {
                    diapatch({
                        type: 'changed',
                        task: {
                            ...task,
                            done: e.target.checked
                        }
                    })
                }}
            />
            {taskContent}
            <button onClick={()=> {
                diapatch({
                    type: 'deleted',
                    id: task.id
                })
            }}>
                Delete
            </button>
        </label>
    )
}

TaskApp.jsx

import {useReducer} from 'react';
import AddTask from './AddTask.jsx';
import TaskList from './TaskList.jsx';
import {TasksContext, TaskDispatchContext} from './TasksContext';

export default function TaskApp() {
    const [tasks, dispatch] = useReducer(
        tasksReducer,
        initialTasks
    );

    return (
        <>
            <TasksContext.Provider value={tasks}>
                <TaskDispatchContext.Provider value={dispatch}>
                <h1>Day off in Kyoto</h1>
                <AddTask />
                <TaskList /> 
                </TaskDispatchContext.Provider>
            </TasksContext.Provider>
           
        </>
    )
}

function tasksReducer(tasks, action) {
    switch (action.type) {
        case 'added': {
            return [...tasks,{
                id: action.id,
                text: action.text,
                done: false
            }];
        }
        case 'changed': {
            return tasks.map(t => {
                if(t.id === action.task.id){
                    return action.task;
                }else {
                    return t;
                }
            })
        }

        case 'deleted': {
            return tasks.filter(t => t.id !== action.id);
        }

        default : {
            throw Error('Unknown action: ' + action.type);
        }   
    }
}

let nextId = 3;
const initialTasks = [
    { id: 0, text: 'Philosopher’s Path', done: true },
    { id: 1, text: 'Visit the temple', done: false },
    { id: 2, text: 'Drink matcha', done: false }
];

(3)## 将相关逻辑迁移到一个文件当中

TaskApp.jsx

import AddTask from './AddTask.jsx';
import TaskList from './TaskList.jsx';
import {TasksProvider} from './TasksContext';

export default function TaskApp() {
   
    return (
        <>
            <TasksProvider>
                <h1>Day off in Kyoto</h1>
                <AddTask />
                <TaskList /> 
            </TasksProvider>
           
        </>
    )
}

TasksContext.jsx

import {createContext, useReducer, useContext} from 'react';

export const TasksContext = createContext(null);
export const TaskDispatchContext = createContext(null);

export function TasksProvider({ children }){
    const [tasks, dispatch] = useReducer(
        tasksReducer,
        initialTasks
    );
    return (
        <>
            <TasksContext.Provider value={tasks}>
                <TaskDispatchContext.Provider value={dispatch}>
                {children}
                </TaskDispatchContext.Provider>
            </TasksContext.Provider>
           
        </>
    )
}

export function useTasks(){
    return useContext(TasksContext);
}

export function useTasksDispatch(){
    return useContext(TaskDispatchContext);
}

function tasksReducer(tasks, action) {
    switch (action.type) {
        case 'added': {
            return [...tasks,{
                id: action.id,
                text: action.text,
                done: false
            }];
        }
        case 'changed': {
            return tasks.map(t => {
                if(t.id === action.task.id){
                    return action.task;
                }else {
                    return t;
                }
            })
        }

        case 'deleted': {
            return tasks.filter(t => t.id !== action.id);
        }

        default : {
            throw Error('Unknown action: ' + action.type);
        }   
    }
}

const initialTasks = [
    { id: 0, text: 'Philosopher’s Path', done: true },
    { id: 1, text: 'Visit the temple', done: false },
    { id: 2, text: 'Drink matcha', done: false }
];

AddTask.jsx

import {useState} from 'react';
import {useTasksDispatch} from './TasksContext';

export default function AddTask () {
    const [text, setText] = useState('');
    const diapatch = useTasksDispatch();
    return (
        <>
           <input 
              placeholder='Add Task'
              value={text}
              onChange={e => setText(e.target.value)}
            />
            <button 
                onClick={()=>{
                    setText('');
                    diapatch({
                        type: 'added',
                        id: nextId++,
                        text: text,
                    })
                }}
            >
                Add
            </button>
        </>
    )
}
let nextId = 3;

TaskList.jsx

import {useState, useContext} from 'react';
import {useTasks, useTasksDispatch} from './TasksContext';

export default function TaskList () {
    const tasks = useTasks();
    return (
        <ul>
            {
                tasks.map(task => (
                    <li key={task.id}>
                       <Task 
                          task={task}
                       />
                    </li>
                ))
            }
        </ul>
    )
}

function Task ({task}) {
    const [isEditing, setIsEditing] = useState(false);
    const diapatch = useTasksDispatch();
    let taskContent;
    if(isEditing) {
        taskContent = (
            <>
                <input 
                    value={task.text}
                    onChange={e=>{
                        diapatch({
                            type: 'changed',
                            task: {
                                ...task,
                                text: e.target.value,
                            }
                        })
                    }}
                />
                <button onClick={()=> setIsEditing(false)}> Save</button>
            </>
        )
    } else {
        taskContent = (
            <>
                {task.text}
                <button onClick={() => setIsEditing(true) }>
                    Edit
                </button>

            </>
        )
    }

    return (
        <label>
            <input 
                type='checkbox'
                checked={task.done}
                onChange={e=> {
                    diapatch({
                        type: 'changed',
                        task: {
                            ...task,
                            done: e.target.checked
                        }
                    })
                }}
            />
            {taskContent}
            <button onClick={()=> {
                diapatch({
                    type: 'deleted',
                    id: task.id
                })
            }}>
                Delete
            </button>
        </label>
    )
}

具体教程可以参考react官网