# 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
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
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
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
2
3
4
5
6
7
8
9
10
11
12
13
# Reducer函数
Reducer函数则是一种函数式编程的概念,用于将一个状态转化为另一个状态,从而实现对应用程序状态的更新操作。Reducer函数接收一个状态和动作(action)对象作为参数,然后根据动作对象的类型来更新状态,并返回更新后的新状态。
Reducer函数的实现应当符合以下原则:
- 状态不可变性:Reducer函数更新状态的方式是根据当前状态克隆出一个新的状态对象,并在新的状态对象上进行修改,而不是在当前状态上直接进行修改。
- 纯函数:Reducer函数应该是纯函数,即其返回值只取决于传入参数的值,不依赖于任何外部因素,也不会对外部环境造成任何影响。
- 单向数据流: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
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
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
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
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25