# Context

每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化。

Provider 接收一个 value 属性,传递给消费组件。一个 Provider 可以和多个消费组件有对应关系。多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据。

// 定义Context类型
interface UserContextType {
  user: string;
  setUser: React.Dispatch<React.SetStateAction<string>>;
}

// 创建Context
const UserContext = createContext<UserContextType | undefined>(undefined);

// 自定义Hook,用于在组件中访问Context
export const useUserContext = () => {
  const context = useContext(UserContext);
  if (!context) {
    throw new Error('useUserContext must be used within a UserContextProvider');
  }
  return context;
};

// 提供Context的组件
const UserContextProvider: React.FC = ({ children }) => {
  const [user, setUser] = useState<string>('');

  return (
    <UserContext.Provider value={{ user, setUser }}>
      {children}
    </UserContext.Provider>
  );
};

export default function App() {
  return (
  	<themeContext.Provider value={{ user, setUser }}>
        <Topics />
    </themeContext.Provider>
  )
}

// in Topic
import { UserContext } from './App'

const Topics: React.FC = () => {
  return <UserContext.Consumer>
    {({ user }) => <h3>{user}</h3>}
  </UserContext.Consumer>
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

使用useContext能够更简单

import { useContext } from 'react'
import { UserContext } from './App'

const Topics: React.FC = () => {
  const ctx = useContext(UserContext)
  return <h3>{ctx.user}</h3>
}
1
2
3
4
5
6
7

# context作为全局状态以及修改context

context可以用来作为全局状态,不过context本身是只读的。

不过我们可以通过Context.Provider 的value属性来达到修改context的目的, value 属性是用来传递一个值给所有被这个 Context 包裹的组件的。

export const CountContext = createContext({ count: 0 })
export default function App() {
  const [count, setCount] = useState(0)
  
  return (
    <CountContext.Provider value={{ count }}>
    	<Topics />
    </CountContext.Provider>
  )
}

// in Topics
import { CountContext } from './App'

const Topics: React.FC = () => {
  return <CountContext.Consumer>{(ctx) => ctx.count}</CountContext.Consumer>
}

// 或者使用useContext
const Topics: React.FC = () => {
  const ctx = useContext(CountContext)
  return <h3>{ctx.count}</h3>
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

如果想在子组件内修改context,可以传一个方法用于修改

export default function App() {
  const context = useContext(CountContext)
  const [count, setCount] = useState(context.count)
  context.incrementCount = () => {
    setCount(count + 1)
  }
  
  return (
    <CountContext.Provider value={{ count, incrementCount }}>
    	<Topics />
    </CountContext.Provider>
  )
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# Reducer函数

Reducer函数则是一种函数式编程的概念,用于将一个状态转化为另一个状态,从而实现对应用程序状态的更新操作。Reducer函数接收一个状态和动作(action)对象作为参数,然后根据动作对象的类型来更新状态,并返回更新后的新状态。

Reducer函数的实现应当符合以下原则:

  1. 状态不可变性:Reducer函数更新状态的方式是根据当前状态克隆出一个新的状态对象,并在新的状态对象上进行修改,而不是在当前状态上直接进行修改。
  2. 纯函数:Reducer函数应该是纯函数,即其返回值只取决于传入参数的值,不依赖于任何外部因素,也不会对外部环境造成任何影响。
  3. 单向数据流:Reducer函数的作用是对当前状态进行转换,从而输出一个新的状态对象作为下一次输入的状态,实现一种单向数据流。在更新状态时,应根据旧状态和动作对象计算出新状态,而不是直接修改旧状态。

下面是一个简单的Reducer函数:

function counterReducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      return state;
  }
}
1
2
3
4
5
6
7
8
9
10

Reducer函数通常与React的useReducer Hook搭配使用,将其作为useReducer Hook的第一个参数传递进去。最终可以通过dispatch函数派发一个动作对象,来触发Reducer函数的执行,从而实现对应用程序状态的更新。

# 手动触发reducer

function counterReducer(state: State, action: Action) {
    switch (action.type) {
      case 'increment':
        return { count: state.count + 1 }
      case 'decrement':
        return { count: state.count - 1 }
      default:
        return state
    }
  }
const initialState: State = { count: 0 }
const [state, setState] = useState(initialState)
const dispatch = (action: Action) => setState(counterReducer(state, action))

return <button onClick={() => dispatch({ type: 'increment' })}>{state.count}</button>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# useReducer触发

useReducer的源码很简单,useReducer实际上就是把上面一个过程封装起来

function useReducer(reducer, initialState) {
  const [state, setState] = useState(initialState)
  const dispatch = (action) => {
    const nextState = reducer(state, action)
    setState(nextState)
  }
  return [state, dispatch]
}
1
2
3
4
5
6
7
8

# 通过useReducer来配合Context

interface State {
  count: number
}
type Action = { type: 'increment' } | { type: 'decrement' } | { type: 'reset' }
interface ContextType {
  state: State
  dispatch: React.Dispatch<Action>
}

export const MyContext = createContext<ContextType>({
  state: { count: 0 },
  dispatch: () => {}
})
const myReducer = (state: State, action: Action) => {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 }
  }
}
export default function App() {
  const [state, dispatch] = useReducer(myReducer, { count: 100 })
  return (
  	<MyContext.Provider value={{ state, dispatch }}>
      <About />
    </MyContext.Provider>
  )
}

// in About
const About: React.FC = () => {
  const {state,dispatch} = useContext(MyContext)
  console.log('ctx: ', ctx)
  // return <CountContext.Consumer>{(ctx) => ctx.count}</CountContext.Consumer>
  return (
    <>
      <button onClick={() => dispatch({ type: 'increment' })}>reducer</button>
      <h3>{state.count}</h3>
    </>
  )
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

# 从Storage中获取全局状态和context配合使用

interface IGlobalContext {
  isLogin: boolean;
  setLogin: React.Dispatch<React.SetStateAction<boolean>>;
}
// 创建context并分配默认值
export const globalContext = createContext<IGlobalContext>({
  isLogin: JSON.parse(localStorage.getItem('isLogin') || 'false'),
  setLogin: () => {}
});

export default function App() {
  const [isLogin, setLogin] = useState<boolean>(() => {
    const storeValue = localStorage.getItem('isLogin');
    return storeValue ? JSON.parse(storeValue) : false;
  });
  return (
    <div className='App'>
      <globalContext.Provider value={{ isLogin, setLogin }}>
        <Suspense fallback={<div>loading...</div>}>
          <RouterProvider router={router} />
        </Suspense>
      </globalContext.Provider>
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25