useContext
语法:
somecontext = createContext(defaultValue);
作用: 避免了组件嵌套太深, 顶层变量层层传递的麻烦.
第一步: 用createContext声明一个context上下文变量
import { createContext } from 'react';
export const GlobalContext = createContext({});
export const GlobalContextProvider = GlobalContext.Provider;
export default GlobalContext;
● 参数defaultValue: 如果在组件的顶层树没有匹配到context Provider,就会使用defaultValue,它是静态的并且永远不会被改变. 如果你没有什么特别想声明的默认值, 写个null就行.
● 返回值: 返回一个context对象, 它本身不带有任何信息,它代表了其他组件可以提供和读取的上下文.生成的context对象有两个属性,分别是:
○ context.Provider: 让你用来给组件们提供context变量
○ context.Consumer: 另一种比较少用的, 用来消费context变量的方法( useContext出现之前的一种老的读取context的方法, 不推荐使用, 现在推荐用useContext )
第二步: 用context.Provider给下层组件传递要消费的值
像下面这样, 将你的子组件用context.Provider包裹, 并通过value向内部所有的组件们提供可用的值,
import ComponentB from './ComponentB';
const Benson = () => {
const [value, setValue] = useState(0)
return (
<GlobalContextProvider value={{ value}}>
{JSON.stringify(value)}
<ComponentB />
</GlobalContextProvider>
)
}
export default Benson
第三步: 用useContext消费顶层树提供的数据
所有子组件就可以通过调用usecontext(ThemeContext)去读取这个context的变量,无论这些子组件嵌套有多深
import GlobalContext from './Contexts';
const ComponentB = () => {
const { value } = useContext(GlobalContext)
return <div>Component B</div>;
};
export default ComponentB;
只要来自父组件的context改变了, react就会重新渲染和更新ui
如果想在子组件更新这些数据怎么办?
总不能从顶层传递一个setstate下来吧,这多麻烦啊, 特别是如果你的项目中组件嵌套很深的情况, 有个简单的方法,就是从context.Provider的value中 pass down 一个 dispatch 函数 from useReducer via context
import ComponentA from './ComponentA';
import ComponentB from './ComponentB';
const Benson = () => {
const [value, dispatch] = useReducer((pre, action) => {
return { ...pre, ...action }
}, { a: 1, b: 2 })
return (
<GlobalContextProvider value={{ value, dispatch }}>
{JSON.stringify(value)}
<ComponentB />
<ComponentA />
</GlobalContextProvider>
)
}
export default Benson
这样TodosApp中所有的子组件就可以使用dispatch去更新顶层数据了
import GlobalContext from './Contexts';
const ComponentB = () => {
const { value, dispatch } = useContext(GlobalContext)
const handelDispatch = () => {
dispatch({ c: 3 })
}
return <div onClick={handelDispatch}>Component B</div>;
};
export default ComponentB;
问题: ComponentB中dispatch一个aciton,是否会导致用memeo包裹的ComponentA重新渲染
const ComponentA = memo(({ data }) => {
// 自定义比较函数
console.log('重新渲染ComponentA组件');
return <div >Component A</div>;
});
export default ComponentA;
答案: 不会
redux
1. 安装 Redux
npm install redux react-redux
2. 创建 Redux Store
Redux store 是存储应用状态的地方。你需要定义一个 reducer 来描述如何根据动作更新状态。
import { createStore } from 'redux';
// 定义初始状态
const initialState = {
count: 0
};
// 定义 reducer
const reducer = (state = initialState, action) => {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 };
case 'DECREMENT':
return { ...state, count: state.count - 1 };
default:
return state;
}
};
3. 提供 Redux Store 给 React 应用
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './App'; // 假设 App 是你的根组件
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
4. 连接 React 组件和 Redux
import React from 'react';
import { connect } from 'react-redux';
// 定义 React 组件
const Counter = ({ count, increment, decrement }) => (
<div>
<h1>{count}</h1>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
);
// 定义 mapStateToProps 函数
const mapStateToProps = state => ({
count: state.count
});
// 定义 mapDispatchToProps 函数
const mapDispatchToProps = dispatch => ({
increment: () => dispatch({ type: 'INCREMENT' }),
decrement: () => dispatch({ type: 'DECREMENT' })
});
// 使用 connect 将组件连接到 Redux store
export default connect(mapStateToProps, mapDispatchToProps)(Counter);
react-redux 的核心组件只有两个,Provider 和 connect。
Provider
Provider 其实就只是一个外层容器,它的作用就是通过配合 connect 来达到跨层级传递数据。使用时只需将Provider定义为整个项目最外层的组件,并设置好store。那么整个项目都可以直接获取这个store。它的原理其实是通过React中的Context来实现的。
connect
connect 的作用是连接React组件与 Redux store,它包在我们的容器组件的外一层,它接收上面 Provider 提供的 store 里面的 state 和 dispatch,传给一个构造函数,返回一个对象,以属性形式传给我们的容器组件。
它共有四个参数mapStateToProps, mapDispatchToProps, mergeProps以及options。
mapStateToProps 的作用是将store里的state(数据源)绑定到指定组件的props中
mapDispatchToProps 的作用是将store里的action(操作数据的方法)绑定到指定组件的props中
在现代的 React 开发中,使用 React Redux 提供的 connect 函数已经逐渐被 React Redux 的 useSelector 和 useDispatch hooks 所取代。这些 hooks 提供了一种更加简洁和直观的方式来访问 Redux store,并且更符合函数组件的编写习惯。
利用useSelector/useDispatch的写法
counterSlice.js. //用来定义reducer
import { createSlice } from '@reduxjs/toolkit';
export const counterSlice = createSlice({
name: 'count',
initialState: {
value: 1,
},
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
},
},
});
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;
rootReducer.js(将上面定义的reducer引入rootReducer中)
import { combineReducers } from '@reduxjs/toolkit';
import counterSlice from './counterSlice';
const rootReducer = combineReducers({
counter: counterSlice,
a: aSlice //可以设置多个reduce
});
export default rootReducer;
3. 利用configureStore创建一个store
import { configureStore } from '@reduxjs/toolkit';
import rootReducer from './rootReducer';
const store = configureStore({
reducer: rootReducer,
});
export default store;
//Counter.js(消费这个store)
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement, incrementByAmount } from '../counterSlice';
function Counter() {
const count = useSelector((state) => { console.log(state); return state.counter.value });
const dispatch = useDispatch();
return (
<div>
<h1>{count}</h1>
<button onClick={() => dispatch(increment())}>Increment</button>
<button onClick={() => dispatch(decrement())}>Decrement</button>
<button onClick={() => dispatch(incrementByAmount(2))}>Increment by 2</button>
</div>
);
}
export default Counter;
区别
- 依赖和复杂度:
useContext 和 useReducer 是 React 内置的,不需要额外的依赖。
Redux 是一个独立的库,需要额外安装和配置。
数据流和架构:
useContext 和 useReducer 的数据流相对简单,适合中小型应用。
Redux 提供更强大的中间件支持和开发工具,适合大型复杂应用。
- 开发工具:
Redux 拥有丰富的开发工具(如 Redux DevTools),便于调试和状态管理。
useContext 和 useReducer 调试相对不那么方便,但可以借助 React DevTools。
中间件和扩展性:
Redux 支持中间件(如 redux-thunk、redux-saga)来处理异步操作和副作用。
useContext 和 useReducer 需要自定义解决异步问题,缺乏内置的中间件机制。
- 全局状态管理:
Redux 更加适合需要全局一致性和复杂状态管理的应用。
useContext 和 useReducer 更适合局部或中小型状态管理。
参考自文档: https://legacy.reactjs.org/docs/hooks-faq.html#how-to-avoid-passing-callbacks-down