# redux进阶

# UI组件 和 容器组件

ui组件主要是render函数那部分内容,渲染页面样式的组件,而容器组件则是逻辑部分。

import React,{Component} from 'react'
import {Input,Button,List} from 'antd'
class AppUI extends Component {
    render(){
        return (
            <div style={{marginTop:'10px',marginLeft:'10px'}}>
                <Input style={{width:'300px'}} 
                       placeholder="请输入内容" 
                       onChange={this.props.inputchange}
                       value={this.props.inputValue}
                       />
                <Button 
                  type="primary"
                  style={{marginLeft:'10px'}}
                  onClick={this.props.btn}
                  >提交</Button>
                <List
                size="small"
                style={{width:'300px',marginTop:'10px'}}
                bordered
                dataSource={this.props.list}
                renderItem={(item,i) => (
                   <List.Item style={{cursor:'pointer'}} onClick={(i)=>{
                     this.props.del(i)
                   }}>{item}</List.Item>
                )}
              />
            </div>
          );
    }
}

export default AppUI;
//父组件,包括state和一些操作方法,都可以传给子组件 
 render() {
    return (
      <AppUI 
        inputchange={this.inputchange}
        inputValue={this.state.inputValue}
        btn={this.btn}
        list={this.state.list}
        del={this.del}
      />
    );
  }

# 无状态组件

当一个普通组件只有render函数时,完全可以改写成一个无状态组件的形式

  • 无状态组件的性能比较高
  • 接受参数props
const AppUI=(props)=>{
  return (
    <div style={{marginTop:'10px',marginLeft:'10px'}}>
       。。。
    </div>
  );
} 
export default AppUI;

# redux中间件

# redux-thunk

cnpm install redux-thunk --save

可以在actionCreators里通过返回一个函数,然后就可以在函数里编写某些异步操作了,待异步操作结束,最后通过传入的store.dispatch,发出action通知给Store要进行状态更新。

//store/index.js
 import {createStore,compose,applyMiddleware} from 'redux'
 import reducer from './reducer.js'
 import thunk from 'redux-thunk'
 
 const composeEnhancers =window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?   
     window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
 	
 const enhancer = composeEnhancers(
   applyMiddleware(thunk),
 );
 //如果不需要使用多个中间件,如不使用window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__,
 //createStore第二个参数可以直接写成applyMiddleware(thunk)
 const store =createStore(
 reducer,
 enhancer
 ); 
 
 export default store; 
  • 返回给store的每个action一般只能写成对象的形式,但是通过使用react-thunk之后,action可以返回函数,而且不需要引入store因为他自带dispatch这个方法
//actionCreators.js
 export const getInputValue=(value)=>({
     type:'change_input_value',
     value
 })
 export const orgin=(value)=>({
 	type:"orgin",
 	value
 })
 
 // 如果没有配置react-thunk,那么在actionCreator中使用返回的是函数会报错
 // Actions must be plain objects. Use custom middleware for async actions.
 // 还可以进行异步操作,比如像后台发起请求等候反馈结果
 export const getorgin= ()=>{
 	return (dispatch) => {
 		setTimeout(()=>{
 			const res=[1,2,3,4,5,6];
 			const action=orgin(res)
 			dispatch(action)
 		},3000)
 	}
 }
//App.js
 componentDidMount(){
 	const action =getorgin();
 	store.dispatch(action)
 }

# redux-saga

redux-saga 是一个用于管理 Redux 应用异步操作的中间件(又称异步 action)。 redux-saga 通过创建 Sagas 将所有的异步操作逻辑收集在一个地方集中处理。

  • Reducers 负责处理 action 的 state 更新
  • Sagas 负责协调那些复杂或异步的操作

没使用redux-saga,action接收只能在reducer,而现在可以在sagas.js中也可以接收到。

redux增加中间件处理副作用后的数据流大致如下:UI——>action(side function)—>middleware—>action(plain)—>reducer—>state—>UI

在有副作用的action和原始的action之间增加中间件处理,从图中我们也可以看出,中间件的作用就是:转换异步操作,生成原始的action,这样,reducer函数就能处理相应的action,从而改变state,更新UI。

# redux-saga的关键字

# fork:创建一个新的进程或者线程,并发发送请求。

function* user() {
    yield takeEvery('FETCH_REQUEST', fetch_user); // 监听 FETCH_REQUEST action
}

// 并发发送请求
function* fetch_user() {
    const [users, todos] = [
        yield fork(fetchResource, 'https://jsonplaceholder.typicode.com/users'),
        yield fork(fetchResource, 'https://jsonplaceholder.typicode.com/todos')
    ]
}

function* fetchResource(resource) {
    const data = yield call(axios.get, resource);
    // 获取 call 数据,触发成功后的 action
    yield put({ type: 'FETCH_SUCESS', uu: data });
}

# call:发送 api 请求

# put:发送对应的 dispatch,触发对应的 action

# takeEvery:监听对应的 action;每一次 dispatch 都会触发;例如:点击一个新增的按钮,2s 后触发新增动作,在2s内不断点击按钮,这时候,每一次点击,都是有效的。

# takeLatest:监听对应的 action;只会触发最后一次 dispatch;例如:点击一个新增的按钮,2s 后触发新增动作,在2s内不断点击按钮,这时候,只有最后一次点击是有效的。

# all:跟 fork 一样,同时并发多个 action,没有顺序。

const rootUser = [
    user(),
    todo()
];

yield all([ // 同时并发多个
    ...rootUser, // 
    add()
]);
cnpm install redux-saga --save
  • createSagaMiddleware(options) 创建一个 Reudx middlerware,并将sagas链接到Redux Store,通过第三个参数传入。
  • middleware.run(saga,...args) 动态运行saga,只能用于在applyMiddleware运行
    • saga:一个generator函数
    • arg:提供saga的参数
import {createStore,compose,applyMiddleware} from 'redux'
import reducer from './reducer.js'
import mySaga from "./sagas.js"
import createSagaMiddleware from 'redux-saga'
const sagaMiddleware = createSagaMiddleware()
createStore(reducer,{},applyMiddleware(sagaMiddleware))
sagaMiddleware.run(mySaga)

 //store/index.js 
 import {createStore,compose,applyMiddleware} from 'redux'
 import reducer from './reducer.js'
 
 import createSagaMiddleware from 'redux-saga'
 import mySaga from "./sagas.js"
 
 const sagaMiddleware=createSagaMiddleware();
 const composeEnhancers =window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?   
     window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
 	
 const enhancer = composeEnhancers(
   applyMiddleware(sagaMiddleware),
 );
 
 const store =createStore(
 reducer,
 enhancer
 ); 
 
 sagaMiddleware.run(mySaga)
 
 export default store; 
//App.js 
componentDidMount(){
			
			const action ={
				type:"getorgin"
			};
			console.log(action)
			store.dispatch(action)
		}
 import {takeEvery,put} from 'redux-saga/effects'
  function* doit(v){
 	 let res=[1,2,3,4,5,6,7,8];
 	 const action={
 		 type:'orgin',
 		 value:res
 	 }
 	 yield put(action)
  }
  
 function* mySaga(){
 	yield takeEvery('getorgin',doit)
 }
 
 export default mySaga
import React from 'react';
import ReactDOM from 'react-dom';
import {createStore, applyMiddleware} from 'redux'
import createSagaMiddleware from 'redux-saga'

import rootSaga from './sagas'
import Counter from './Counter'
import rootReducer from './reducers'

const sagaMiddleware = createSagaMiddleware()
let middlewares = []
middlewares.push(sagaMiddleware)

const createStoreWithMiddleware = applyMiddleware(...middlewares)(createStore)
const store = createStoreWithMiddleware(rootReducer)

sagaMiddleware.run(rootSaga)

const action = type => store.dispatch({ type })

function render() {
  ReactDOM.render(
  <div>
	  <h1>redux-saga</h1>
	  <Counter
		value={store.getState()}
		onIncrement={() => action('INCREMENT')}
		onDecrement={() => action('DECREMENT')}
		onIncrementAsync={() => action('INCREMENT_ASYNC')} />
  </div>
    ,
    document.getElementById('root')
  )
}

render()

store.subscribe(render)
/**
 * Created by guangqiang on 2017/12/17.
 */
import { put, call, take,fork ,takeEvery, takeLatest } from 'redux-saga/effects';
// import {} from 'redux-saga'

export const delay = ms => new Promise(resolve => setTimeout(resolve, ms));

function* incrementAsync() {
  // 延迟 1s 在执行 + 1操作
  yield call(delay, 1000);
  yield put({ type: 'INCREMENT' });
}

export default function* rootSaga() {
  // 如果没有while循环,只会执行一次;那样上面的onIncrementAsync只会执行一次
  // while(true){
  //   yield take('INCREMENT_ASYNC');
  //   yield fork(incrementAsync);
  // }

  // 下面的写法与上面的写法上等效
  yield takeEvery("INCREMENT_ASYNC", incrementAsync)
}

总结

  • redux-thunk
    • 需要在indexjs中引入redux的compose,applyMiddleware这两个组件
    • 它的作用是让actionCreators能返回一个函数
  • redux-saga
    • 需要更多的配置,同时新建一个sagas.js文件来处理接受到的action
    • 使用generator函数,注意语法
    • 如果sagas.js和reducer中都对同一个action.type进行了处理,如果结果中有些改了同一个值的话,可能sagas.js最后会覆盖掉reducer的操作, 所以必须尽量避免这种情况

# react-redux

相对于只使用redux,要频繁手动订阅取消订阅,派发dispatch等操作,react-redux就轻松多了

cnpm install react-redux --save

react-redux提供了两个api

  1. Provider 为后代组件提供store
  2. connect 为组件提供数据和变更方法
  3. useSelector 和 useDispatch 钩子,供函数组件使用

provider绑定了store后,它包裹的组件都可以访问store里的信息,

 import {Provider} from 'react-redux';
 import store from './store';
 const Apps=(
 	<Provider store={store}>
 		<App />
 	</Provider>
 )
 
 ReactDOM.render(Apps , document.getElementById('root'));
  • 在使用的界面中配置,使用connect进行连接 ,注意结尾的写法使用connect
  • 默认的dispatch方法其实是不需要通过mapDispatchToProps(名字随便取)导出的,但是利用mapDispatchToProps可以自定义dispatch名称,可以使对象形式,也可以是函数形式,函数形式默认接受一个参数dispatch
  • 在组件中用这些需要解构props,来获取这些数据
import React,{Component} from 'react'
import {connect} from 'react-redux'
class Reactredux extends Component{
	render() {
	 const { num,list, add, minus } = this.props;
	 return (
		 <div>
			 <h1>ReactReduxPage</h1>
			 <p>{num}</p>
			 <p>{list}</p>
			 <button onClick={add}>add</button>
			 <button onClick={minus}>minus</button>
		 </div>
	 );
	 }
}

const mapStateToProps = state => {
 console.log(state)
 return {
	num:state.counter,
	list:state.china
 };
};


// const mapDispatchToProps =(dispatch)=> {
// 	return{
// 		minus(){
// 			const action = {
// 				type:'INCREMENT'
// 			}
// 			dispatch(action)
// 		}
// 	}
// };


const mapDispatchToProps = {
	add: () => {
		return { type: "ADD_TODO" ,text:'19999'};
	}	
};

export default connect(
	 mapStateToProps, //状态映射 mapStateToProps
	 mapDispatchToProps, //派发事件映射
)(Reactredux);

redux-thunk解读 (opens new window) redux-saga文档 (opens new window) redux-saga解读 (opens new window) redux-saga合并 (opens new window)

# Redux Toolkit @reduxjs/toolkit

Redux Toolkit是Redux官方推荐的构建Redux逻辑的方式,它提供了更加简化、更加一致的API来管理Redux应用的状态。

通常结合react-redux用

npm install @reduxjs/toolkit react-redux
# 或者
yarn add @reduxjs/toolkit react-redux

1.在项目的根目录或 store 文件夹下创建一个 index.ts 或 index.js 文件,用于配置和导出 Redux store。

// store/index.js
import { configureStore } from '@reduxjs/toolkit';
import userSlice from './slice/userSlice'; // 假设你有一个 userSlice 文件
import mySlice from './slice/mySlice'; // 假设你有一个 mySlice 文件

const store = configureStore({
  reducer: {
    user: userSlice.reducer, // 注意这里的命名空间 user
    my: mySlice.reducer, // 注意这里的命名空间 my
  },
  // 你可以在这里添加额外的中间件配置,但 @reduxjs/toolkit 默认已经包含了 Redux Thunk
});

export default store;
  1. 创建 Slice. Slice 是 Redux Toolkit 引入的一个新概念,它将 action creators 和 reducer 逻辑组合在一起,使得每个模块化的 Redux 逻辑都封装在一个 slice 文件中。
// store/slice/userSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';

// 定义一个异步函数,例如从服务器获取用户数据
export const fetchUserData = createAsyncThunk(
  'user/fetchUserData',
  async (userId, thunkAPI) => {
    const response = await fetch(`https://api.example.com/users/${userId}`);
    return response.json();
  }
);

const userSlice = createSlice({
  name: 'user', // slice 的命名空间
  initialState: {
    users: [], // 初始状态
    loading: false,
    error: null,
  },
  reducers: {
    setUser: (state, action) => {
      state.users = action.payload;
    },
  },
  extraReducers: (builder) => {
    builder
      .addCase(fetchUserData.pending, (state) => {
        state.loading = true;
        state.error = null;
      })
      .addCase(fetchUserData.fulfilled, (state, action) => {
        state.loading = false;
        state.users = action.payload;
      })
      .addCase(fetchUserData.rejected, (state, action) => {
        state.loading = false;
        state.error = action.error.message;
      });
  },
});

export const { setUser } = userSlice.actions;
export default userSlice.reducer;
  1. 在组件中使用:在你的 React 组件中,可以使用 useSelector 和 useDispatch 钩子来访问 Redux store 中的状态和派发 actions。
// MyComponent.js
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchUserData, setUser } from '../store/slice/userSlice';

const MyComponent = () => {
  const users = useSelector((state) => state.user.users);
  const loading = useSelector((state) => state.user.loading);
  const dispatch = useDispatch();

  React.useEffect(() => {
    dispatch(fetchUserData(1)); // 假设我们要获取 ID 为 1 的用户数据
  }, [dispatch]);

  return (
    <div>
      {loading ? (
        <p>Loading...</p>
      ) : (
        <ul>
          {users.map((user) => (
            <li key={user.id}>{user.name}</li>
          ))}
        </ul>
      )}
    </div>
  );
};

export default MyComponent;
  1. 在应用入口文件中提供 Store
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './store'; // 导入创建的 Redux store
import App from './App'; // 导入的根组件

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);
最后更新: 12/15/2024, 9:08:57 AM