1、Redux
什么是单项数据流?
Action -> Store -> View
当用户调用stroe.dispatch的时候,会派发一个action。reducer根据action类型和payload更新store里的state。最后是视图更新。(可以理解为,视图更新是subscribe包裹的回调函数,每当数据更新,自动调用subscribe里的回调函数)
使用案例
1.首先创建 store 对象
import { createStore } from 'redux';
const reducer = (prevState= {/*初始状态*/},action) => {
let newState = prevState;
switch(action.type){
case "情况一":
newState.show = false;
//这里是逻辑处理
case "情况二":
newState.show = false;
//这里是逻辑处理
}
}
const store = createStore(reducer);
export default store;
- 每次 state 更新, subcribe里的回调函数会执行:
store.subscribe(()=>{
console.log(store.getState().show)
})
3.调用 dispatch 更新 state。dispatch 内部的对象就是 action
store.dispatch({
type:"我是type",
payload:"我是参数",
})
为什么reducer必须返回新对象
1.redux会比较新state和旧state,如果不同,才会触发订阅者,更新页面。如果直接修改原对象,那么就无法比较新旧state。
2.违背纯函数的理念,不能改变参数(也不能改变全局变量)。
// 纯函数
function add(a, b) {
return a + b;
}
console.log(add(2, 3)); // 输出: 5
console.log(add(2, 3)); // 输出: 5
// 非纯函数
let result = 0;
function addToResult(value) {
result += value;
return result;
}
console.log(addToResult(5)); // 输出: 5
console.log(addToResult(5)); // 输出: 10
3.reduecer设计成纯函数的好处是:让action变得可追踪。因为它保留了历史中的每一个state。
如何实现redux?
发布订阅模式。
订阅者调用subscribe(回调函数)。因此在store内部有一个callbacks数组用于存放所有的回调。
当触发某些事件,调用store.dispatch(action)更新state。因此dispatch内部会执行reducer,获取根据case获取最新state。在更新完state之后,紧接着遍历callbacks数组,依次执行回调。
在回调内部,可以通过store.getState()获取所有状态,因此只需封装一个getState()函数,返回对象的state。
//自定义简单的redux
function createStore(reducer){
var list = [];
var state = reducer()
function subscribe(callback){
list.push(callback);
}
function dispatch(action){
for(var i in list){
state = reducer(state,action);
list[i] && list[i]();
}
}
function getState(){
return state;
}
return {
createStore,
dispatch,
}
}
//reducer样例
const reducer = (preState={
show:true,
//...
},action)=>{
let newState = {...preState}
switch(action.type){
case "第一个case":
newState.show = false;
return newState;
case
// ......
}
combineReducer
Reducer按照业务需求拆分,然后合并为大reducer。
取值的时候也需要改变。原本从store.getState().show返回。现在需要指定store.getState().TabbarReducer.show
dispatch异步action(thunk)
使用步骤
- store.js中重点在于,创建store的时候,第二个参数传入中间件。
// store.js
import { createStore, applyMiddleware, combineReducers } from 'redux';
import thunk from 'redux-thunk';
import messageReducer from './reducers/message';
const rootReducer = combineReducers({
message: messageReducer,
});
const store = createStore(rootReducer, applyMiddleware(thunk));
export default store;
- 导出action有重大差别,普通情况下,导出的action是一个对象。如果是异步操作,导出的atcion是一个函数
// actions/message.js
export const setMessage = (message) => ({
type: 'SET_MESSAGE',
payload: message,
});
// 异步 action creator
export const fetchMessage = () => async (dispatch) => {
// 模拟异步请求
const fetchData = async () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve('Fetched message');
}, 1000);
});
};
const message = await fetchData();
dispatch(setMessage(message));
};
- dispatch时有差别。普通情况下,dispatch接受一个对象。但如果dispatch的数据是通过网络请求获取的,则dispatch的参数是一个函数(可以看出
fetchMessage()返回值是一个函数)。
// FetchMessageButton.js
import React from 'react';
import { useDispatch } from 'react-redux';
import { fetchMessage } from './actions/message';
const FetchMessageButton = () => {
const dispatch = useDispatch();
const handleClick = () => {
dispatch(fetchMessage());
};
return <button onClick={handleClick}>Fetch Message</button>;
};
export default FetchMessageButton;
以下是草稿(可忽略)
当我们要修改store里的值,一般会用store.dispatch(action对象)。默认情况下,action是一个对象,结构如下:
{
type:'我是type',
payload:'我是payload',
}
如果action里的值需要通过网络请求获取,该怎么办?store.dispatch(getAction())
const getAction = () => {
//dispatch可以随便命名,等到异步请求返回后,再调用dispatch
return (dispatch)=>{
axios.get(www.baidu.com).then((data)=>{
dispatch({
type:'我是type',
payload:data,
});
})
}
}
上述方法解决了dispatch的action的payload需要通过网络请求获取的问题。但是直接运行上述代码会报错,因为得先配置中间件redux-thunk。
const store = createStore(reducer,applyMiddleWare(reduxThunk);
调试工具
左边是dispatch(action)action的名字,右侧是修改后的state。
如何使用React-Redux
如果不用React-Redux
若想要获取store的最新值,需要使用store.subscribe()。一旦store的值被更新,回调函数就会执行。
connect函数的作用
如图下所示,经过高阶组件的封装,你的组件props里就会多出a和b两个属性。高阶组件:本质上是一个函数,接受一个组件,返回一个新组件。
const newComponent = connet(()=>{
return {
a:11,
b:22,
})(你的组件)
理解了connect的用法之后,可以读懂以下代码。
count是store里的变量,它直接从props获取而非从store.getState()中获取。
increment和decrement内部调用了dispatch。不再需要通过store.dispatch(你的action对象)来修改state,直接调用props里的increment和decrement。
import { createStore } from "redux";
import { Provider, connect } from "react-redux";
// 定义 reducer
const initialState = { count: 0 };
function counterReducer(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;
}
}
// 创建 Redux store
const store = createStore(counterReducer);
// 定义组件
function Counter(props) {
return (
<div>
<p>Count: {props.count}</p>
<button onClick={props.increment}>Increment</button>
<button onClick={props.decrement}>Decrement</button>
</div>
);
}
// 定义 mapStateToProps 函数
function mapStateToProps(state) {
return { count: state.count };
}
// 定义 mapDispatchToProps 函数
function mapDispatchToProps(dispatch) {
return {
myfunction : fetchMessage(),
increment: () => dispatch({ type: "INCREMENT" }),
decrement: () => dispatch({ type: "DECREMENT" })
};
}
// 将 Counter 组件与 Redux store 连接
const ConnectedCounter = connect(mapStateToProps, mapDispatchToProps)(Counter);
// 将 Provider 组件包裹在 App 组件中,将 Redux store 传递给应用
function App() {
return (
<Provider store={store}>
<ConnectedCounter />
</Provider>
);
}
export default App;
2. Mobx
基本用法
action用于标记方法,所有修改可观测属性的方法,应该用atcion标记。
computed是计算属性,当计算属性依赖的属性发生变化,会重新计算。
import { action } from 'mobx';
class TodoStore {
@observable todos = [];
@action
addTodo(text) {
this.todos.push({ text, completed: false });
}
@action
toggleTodo(index) {
this.todos[index].completed = !this.todos[index].completed;
}
}
@computed
get unfinishedTodos() {
return this.todos.filter(todo => !todo.completed);
}
}
以下是使用方法,每当autorun回调内的依赖发生变化,会重新执行回调。
// 创建TodoStore实例
const todoStore = new TodoStore();
// 使用autorun监听unfinishedTodos的变化并在控制台输出
const disposer = autorun(() => {
console.log('Unfinished todos:', todoStore.unfinishedTodos);
});
// 添加新的待办事项
todoStore.addTodo('Learn MobX');
todoStore.addTodo('Learn React');
// 当你不再需要autorun时,可以调用返回的disposer函数取消监听
disposer();
在React组件中如何使用store
通过@inject和@observe装饰器,可以在this.props获取store对象
@inject("store")
@observer
class APP extends component{
componentDidMount(){
console.log(this.props.store.属性);
}
}
异步操作
以下action属于同步操作,这没有任何问题。
@action
addTodo(text) {
this.todos.push({ text, completed: false });
}
如果action内含有异步操作,那就会报错。普通action内不应该有异步操作。
@action
addTodo(text) {
axio.get(...).then((data)=>{
this.todos.push({ data, completed: false });
})
}
如果有异步操作,请把修改store属性的代码包裹在runInAction里面。
@action
addTodo(text) {
axio.get(...).then((data)=>{
runInAction(this.todos.push({ data, completed: false }));
})
}
或者这样修改,用await代替then。但无论如何,修改store属性的操作都应该被包裹在runInAction内。
response = await axios.get(/* ... */);
const data = response.data;
runInAction(() => {
this.todos.push({ data, completed: false });
在React中如何使用
1.最外层包裹Provider,提供store
<Provider store = {store} >
<App />
</Provider>
2.在类组件中如何使用store
@inject("store")
@observer //装饰器 @observer等同意Redux中的connect
class App extends Component {
console.log(this.props.store);//@observer将store对象注入到props中
}
3.在函数式组件中如何使用store
import { Observer } from 'mobx-react' //Observer负责在state改变时,重新执行内部回调
import Store from 'store' //函数时组件不会自动把store注入props,需要自己引入
export default function Cinemas(props) {
return (
<Observer>
{
()=>{
store.list.map(item => <div>{item}</div>);
}
}
</Observer>
)
}
如何实现Mobx中的observable和autorun
let tempFunction = null;
function observable(obj) {
let map = new Map();
return new Proxy(obj, {
get(target, key) {
if (tempFunction) {
let observerArray = map.get(key) || [];
if (!observerArray.includes(tempFunction)) {
observerArray.push(tempFunction);
map.set(key, observerArray);
}
}
return target[key];
},
set(target, key, newValue) {
target[key] = newValue; // 更新对象的属性值
let callbackArr = map.get(key);
if (callbackArr) {
for (let i = 0; i < callbackArr.length; i++) {
callbackArr[i]();
}
}
return true;
},
});
}
function autorun(callback) {
tempFunction = callback;
callback();
tempFunction = null;
}
3.Redux和Mobx的不同之处
1.原理差异:
Redux 是基于不可变状态和纯函数的,其核心概念是通过使用单一的状态树和纯粹的 reducer 函数来管理应用状态。每当状态发生变化时,都会创建一个新的状态对象,而不是直接修改原有状态。这有助于更容易地追踪状态变化和调试。
MobX 是基于可观察对象和响应式原则的。其核心概念是将应用状态表示为可观察对象,当这些对象发生变化时,MobX 会自动触发相关的副作用。这允许您以更自然的方式管理状态,更接近面向对象编程。
2.使用方式差异: Redux 要求您遵循严格的模式,包括 action、reducer 和 store。这导致了更多的样板代码和较高的学习曲线。但这也使得 Redux 更具可预测性和可维护性。
相比之下,MobX 提供了更简洁的 API 和更灵活的架构。您可以使用装饰器或函数来定义可观察状态和计算值,以及当状态发生变化时要执行的副作用。这使得 MobX 更易于上手和使用。
3.每当store更新,redux对导致子组件更新。因为子组件接受的props更新了。而mobx只有在observe函数包裹内的依赖更新才会导致子组件更新。
VueX
初始准备
在new Vue实例的时候,需要将store对象传给Vue。
使用方法
触发action:
通过 commit 触发对应的 mutation:
比如 store.commit('addN',3),就会触发 addN 这个 mutation:
action的作用
因为我们不能在 mutation 中执行异步操作,所以以下写法是错误的:
因为这样写的话,Vue 调试工具无法追踪它的状态:
异步操作应该放到 action 里面:
getters是什么
类似于计算属性,它本质就是一个函数