Redux
Redux图解
React Components(组件)通过ActionCreators发送state和action给Store,Store再把state和action给Reducers,Reducers根据action的type(类型)拿到新的state给Store,Store最后把更新后的state(新状态)给React Components(组件)。
Redux代码
store
store/index.js
使用createStore()创建store,createStore第一个参数为reducer;第二个参数为composeEnhancers(),负责合并中间件。
/* src/store/index.js */
import { createStore, compose, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import reducer from './reducer'
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducer,
composeEnhancers(
applyMiddleware(thunk)
)
)
export default store;
store/reducer.js
使用combineReducers将所有组件的reducer合并和向外输出。
/* src/store/reducer.js */
import { combineReducers } from "redux";
import { reducer as ideaReducer } from '@/pages/Home/Idea/store/index'
import { reducer as searchReducer } from '@/pages/Search/store/index'
export default combineReducers({
idea:ideaReducer,
search:searchReducer,
})
main.jsx
引入Provider在最外层包裹,并引入store作为参数传递。
/* src/main.jsx */
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import { BrowserRouter } from 'react-router-dom'
import { Provider } from 'react-redux'
import store from './store'
ReactDOM.createRoot(document.getElementById('root')).render(
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
)
Idea组件的store
Idea组件/store/constants.js
constants.js 负责定义actionTypes。
/* Idea组件/store/constants.js */
export const CHANGE_IDEA_LIST = 'CHANGE_IDEA_LIST'
Idea组件/store/actionCreators.js
- actionCreators.js负责将action传给reducer。
changeIdeaList(data)负责生成action(包括action类型和数据)。getIdeaList()负责进行数据请求和将数据通过changeIdeaList(data)再dispatch给reducer。
/* Idea组件/store/actionCreators.js */
import { getIdeaRequest } from "@/api/request"
import * as actionTypes from './constants'
export const changeIdeaList = (data) => ({
type: actionTypes.CHANGE_IDEA_LIST,
data: data
})
// api请求一定放在action中
export const getIdeaList = () => {
return (dispatch) => {
getIdeaRequest()
.then(data => {
const action = changeIdeaList(data);
dispatch(action)
})
}
}
Idea组件/store/reducer.js
组件的reducer.js负责判断action的类型进行对应的操作。
/* Idea组件/store/reducer.js */
import * as actionTypes from './constants'
const defaultState = {
ideaList: [],
}
export default (state = defaultState, action) => {
switch (action.type) {
case actionTypes.CHANGE_IDEA_LIST:
return {
...state,
ideaList: action.data
}
default:
return state
}
}
Idea组件/store/index.js
组件store的index.js负责合并reducer.js和actionCreators.js并向外输出。
/* Idea组件/store/index.js */
import reducer from './reducer'
import * as actionCreators from './actionCreators'
export { reducer, actionCreators }
组件/index.jsx
mapStateToProps(state)负责设置状态;mapDispatchToProps(dispatch)负责设置高阶函数来调用actionCreators里的函数。connect(mapStateToProps, mapDispatchToProps)(Idea)- state和mapDispatchToProps(dispatch)里的函数可以通过props在组件中结构出来使用。
/* Idea组件/index.jsx */
import React, { useEffect } from 'react'
import { connect } from 'react-redux'
import { actionCreators } from './store/index'
import IdeaItem from "./IdeaItem";
function Idea(props) {
const { ideaList } = props
const { getIdeaDataDispatch } = props
useEffect(() => {
getIdeaDataDispatch();
}, [])
return (
<IdeaItem ideaList={ideaList} />
)
}
const mapStateToProps = (state) => {
return {
ideaList: state.idea.ideaList,
}
}
const mapDispatchToProps = (dispatch) => {
return {
getIdeaDataDispatch() {
dispatch(actionCreators.getIdeaList())
},
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Idea)
Search页面实现
Search目录
Search目录包括子组件和store。
Search子组件
SearchInput组件
布局实现
- 搜索框和取消键通过flex布局
display:flex;实现,取消键设置固定宽度,搜索框设置flex:1;自适应获得宽度。 - 搜索框通过flex布局
display:flex;实现,设置justify-content: space-between;实现搜索键和删除键分布在两边,input表单在中间。
/* SearchInput/style.js */
import styled from 'styled-components'
import style from '@/assets/global-style'
export const SearchBox = styled.div`
display:flex;
align-items: center;
margin-top:0.5rem;
font-size: ${style["font-size-m"]};
.search-box{
flex:1;
display: flex;
justify-content: space-between;
border-radius: 1.5rem;
padding: 0.5rem 0.5rem 0.5rem 0.5rem;
background-color: ${style["search-bgcolor"]};
color:${style["search-color"]} ;
>input{
flex:1;
border:0;
padding: 0 0.5rem;
background-color: ${style["search-bgcolor"]};
font-size: ${style["font-size-l"]};
}
}
.back{
margin-left: 1rem;
}
`
父子组件传值
父组件Search给子组件SearchInput传query、searchBack(修改CSSTransition的show实现页面切换返回)、handleQuery (调用setQuery修改query的值)。
/* Search/index.jsx */
import { CSSTransition } from 'react-transition-group';
function Search(props) {
const [query, setQuery] = useState('')
const searchBack = () => {
setShow(false);
}
const handleQuery = (q) => {
setQuery(q)
}
return (
<CSSTransition
in={show}
timeout={300}
appear={true}
classNames="fly"
unmountOnExit
onExit={() => {
navigate(-1)
}}>
......
<SearchInput
back={searchBack}
newQuery={query}
handleQuery={handleQuery}
/>
......
</CSSTransition>
}
/* SearchInput/index.jsx */
const SearchInput = (props) => {
const { newQuery } = props;
const { handleQuery, back } = props;
}
子组件实现
- 使用
useMomo(缓存上一次函数的计算结果)和debounce(防抖节流)进行性能优化。 - 定义
handleChange函数实现将SearchInput组件的query更新为表单的输入值并渲染,使用onChange事件使得表单一改变值就修改query的值。 - 使用
useEffect(() => { handleQueryDebounce(query) }, [query])修改父组件中query的值。 - 父组件的query被修改后,传给子组件的query也改变了,此时表单内的值和组件自己的query也通过
useEffect实时更新。
/* SearchInput/index.jsx */
import React, { memo, useState, useEffect, useRef, useMemo } from 'react';
import { debounce } from '@/api/utils';
import { SearchBox } from './style'
const SearchInput = (props) => {
const { newQuery } = props;
const { handleQuery, back } = props;
const queryRef = useRef();
const [query, setQuery] = useState('');
// 父组件传过来的函数封装一下
// 优化再升级
// useMomo 可以缓存 上一次函数计算的结果
let handleQueryDebounce = useMemo(() => {
return debounce(handleQuery, 500)
}, [handleQuery])
// mount
useEffect(() => {
// 挂载后
queryRef.current.focus();
}, [])
// 使用useEffect 去更新
useEffect(() => {
handleQueryDebounce(query)
}, [query])
useEffect(() => {
// mount 时候 执行 父组件 newQuery -> input query
let curQuery = query;
if (newQuery !== query) {
curQuery = newQuery;
queryRef.current.value = newQuery;
}
setQuery(curQuery)
// newQuery 更新时执行
}, [newQuery])
const clearQuery = () => {
setQuery('');
queryRef.current.value = "";
queryRef.current.focus();
}
const handleChange = (e) => {
let val = e.currentTarget.value
setQuery(val)
}
const displayStyle = query ? { display: 'block' } : { display: 'none' };
return (
<SearchBox>
<div className="search-box">
<i className='iconfont icon-sousuo'></i>
<input type="text" placeholder='搜索知乎内容' ref={queryRef}
onChange={handleChange} />
<i className='iconfont icon-quxiao'
style={displayStyle}
onClick={clearQuery}></i>
</div>
<div className='back' onClick={() => back()}>取消</div>
</SearchBox>
)
}
export default memo(SearchInput)
OldSearch组件
布局实现
OldSearch组件分成上下俩个部分:
- 上面部分通过flex布局
display:flex;实现,设置justify-content: space-between;实现历史搜索和删除键分布在两边; - 下面部分通过flex布局
display:flex;实现,设置flex-wrap:wrap;实现溢出换行,对每一项设置overflow: hidden;white-space: nowrap;text-overflow: ellipsis;实现溢出显示省略号。
/* OldSearch/style.js */
import styled from 'styled-components'
import style from '@/assets/global-style'
export const OldSearchWrapper = styled.div`
font-size: ${style["font-size-m"]};
.header{
display: flex;
justify-content: space-between;
align-items: center;
margin:0.5rem 0;
.header-left{
font-weight: 700;
}
}
.old-search-body{
display: flex;
flex-wrap:wrap;
.old-search-item{
margin:0 0.2rem 0.5rem 0;
padding: 0.3rem 0.5rem;
border-radius:1rem;
background-color:${style["search-bgcolor"]};
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
`
SearchFind组件
布局实现
SearchFind组件分成上下俩个部分:
- 上面部分通过flex布局
display:flex;实现,设置justify-content: space-between;实现搜索发现和换一换键分布在两边; - 下面部分通过flex布局
display:flex;实现,设置flex-wrap:wrap;实现溢出换行,对每一项设置适当的宽度实现一行排2个。
SearchRecommend组件
页面实现
引入antd-moblie的Tabs实现组件页面。
/* SearchRecommend/index.jsx */
import { Tabs } from 'antd-mobile'
......
<Tabs
activeLineMode='fixed'
style={{
"--content-padding": 0,
"--active-line-height": "0.1rem",
"--fixed-active-line-width": "0.7rem",
'--title-font-size': '0.7rem',
"--active-title-color": `${style["color-light"]}`,
"--active-line-color": `${style["theme-color"]}`,
"color": `${style["search-color"]}`,
}}>
<Tabs.Tab title='热搜影视' key='hotSearchVedio'>
{renderHotsearchCommend(HotsearchVedioList)}
</Tabs.Tab>
<Tabs.Tab title='热搜游戏' key='hotSearchGame'>
{renderHotsearchCommend(HotsearchGameList)}
</Tabs.Tab>
</Tabs>
换一换功能实现
使用Math.random()生成0~1之间的随机数,并乘以后端数据数组长度并使用Math.floor()向下取整随机生成可匹配数组下标的随机数。对随机数数组进行去重处理if (arr.indexOf(cur) === -1)。
/* SearchFind/index.jsx */
const [data, setData] = useState([])
const changedata = () => {
let len = searchFindList.length;
let arr = [];
arr.push(Math.floor(Math.random() * len))
while (arr.length < 8) {
let cur = Math.floor(Math.random() * len)
if (arr.indexOf(cur) === -1) {
arr.push(cur);
}
}
setData(arr);
}
SearchItem组件
搜索组件实现
- 用2个
SearchShowWrapper分别包裹除搜索框的其他组件和搜索内容组件。 - 根据搜索框是否输入值设置相反的boolean值
display:${props=>props.isNone?"none":"block"};来使得未搜索时显示其他组件;搜索框有输入时,其他组件消失,搜索内容组件显示。
/* style.js */
import styled from 'styled-components'
import style from '@/assets/global-style'
export const SearchShowWrapper = styled.div`
display:${props=>props.isNone?"none":"block"};
`
/* index.jsx */
<SearchShowWrapper isNone={query ? false : true}>
<SearchItem query={query} searchItemList={searchItemList}/>
</SearchShowWrapper>
<SearchShowWrapper isNone={query ? true : false}>
<OldSearch oldSearchList={oldSearchList} />
<SearchFind searchFindList={searchFindList} />
<SearchRecommend
HotsearchVedioList={HotsearchVedioList}
HotsearchGameList={HotsearchGameList}
/>
</SearchShowWrapper>
搜索关键字功能实现
通过正则和filter实现模糊搜索。
searchItemList.filter((item, index) => {
return true == new RegExp('^.*' + query + '.*$').test(item)
})
源代码
“我正在参加「创意开发 投稿大赛」详情请看:掘金创意开发大赛来了!”