箭头函数返回对象字面量
我的小盲点,详细内容请参阅MDN箭头函数(对象字面量)
var func = () => ({foo: 1});
redux和react-redux依赖
App.js引入provider
import React, { Component } from "react";
import { Globalstyle } from "./style.js";
import { GlobalFont } from "./statics/iconfont/iconfont.js";
import { Provider } from "react-redux";
import Header from "./common/header/index.js";
import store from "./store";
class App extends Component {
render() {
return (
<Provider store={store}>
<Globalstyle></Globalstyle>
<GlobalFont/>
<Header/>
</Provider>
);
}
}
export default App;
创建store文件夹,写index.js和reducer.js
- header组件中的state删除,放到reducer的defaultState中
- 删掉handleBlur()、 handleFocus() 函数,因为不需要借助setState改变state了
- 把所有this.state改成this.props,将两个函数,放在mapDispatchToProps(){}中
- 写reducer函数,最后,header就变成了一个无状态组件,改用函数来写,效率更高
store的index.js文件夹内容最终如下:
import { legacy_createStore as createStore, compose } from "redux";
import reducer from "./reducer"
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducer,composeEnhancers());
export default store;
一开始redux-devtools没生效,我也没注意到,其实redux-devtools生效的话,浏览器右上角的图标会点亮
//Header组件的最终内容
import React from "react";
import { connect } from "react-redux";
import { HeaderWrapper, Logo, Nav, Navitem, NavSearch, Addition, Button, SearchWrapper } from "./style";
import { CSSTransition } from "react-transition-group";
const Header = (props) => {
return (
<HeaderWrapper>
<Logo href="/" />
<Nav>
<Navitem className="left active">
<span className="iconfont"></span>
首页
</Navitem>
<Navitem className="left">下载APP</Navitem>
<SearchWrapper>
<CSSTransition
in={props.focused}
timeout={200}
classNames="slide"
>
<NavSearch
className={props.focused ? "focused" : ""}
onFocus={props.handleFocus}
onBlur={props.handleBlur}
></NavSearch>
</CSSTransition>
<span className={props.focused ? "focused iconfont" : "iconfont"}></span>
</SearchWrapper>
<Navitem className="right">登录</Navitem>
<Navitem className="right">
<span className="iconfont"></span>
</Navitem>
</Nav>
<Addition>
<Button className="writting">
<span className="iconfont"></span>
写文章
</Button>
<Button className="reg">注册</Button>
</Addition>
</HeaderWrapper >
)
}
const mapStateToProps = (state) => {
return {
focused: state.focused
}
}
const mapDispatchToProps = (dispatch) => {
return {
handleFocus() {
const action = {
type: "search_focus",
};
dispatch(action);
},
handleBlur() {
const action = {
type: "search_blur",
};
dispatch(action);
}
}
}
export default connect(mapStateToProps,mapDispatchToProps)(Header);
//reducer.js最终内容
const defaultState = {
focused: false
}
export default (state = defaultState, action) => {
let newState = { ...state };
if (action.type == "search_focus") {
newState.focused = true;
return newState;
}
if (action.type == "search_blur") {
newState.focused = false;
return newState;
}
return state;
}
;
combineReducer的使用
使用原因:当数据量比较大的时候,reducer中的数据会非常庞杂
改进:各个组件专用的数据,就存放在该组件所在文件夹下的store中,最后再在总store中集中引用
- 在header文件夹下新建一个store文件,其中包含index.js和reducer.js
//index.js
import reducer from "./reducer"
export { reducer };//注意这里不能用default,否则会出错,暂时不清楚原因
//reducer.js文件就是原来的总的reducer,剪切过来的
const defaultState = {
focused: false
}
export default (state = defaultState, action) => {
let newState = { ...state };
if (action.type == "search_focus") {
newState.focused = true;
return newState;
}
if (action.type == "search_blur") {
newState.focused = false;
return newState;
}
return state;
}
;
- 原来的总store的reducer.js应用combineReducers
import { combineReducers } from "redux"
import { reducer as headReducer } from "../common/header/store"
const reducer= combineReducers({
header: headReducer
})
export default reducer;
这样原来的一层state结构变成了两层,多了一个header,表明这是专属于header的state
3. 修改header中index.js文件的mapStateToProps函数
const mapStateToProps = (state) => {
return {
focused: state.header.focused//原来是state.focused
}
}
actionCreator和actionType(constants)文件的创建
- 在header/store下创建actionCreator
export const searchFocus = () => ({
type:"search_focus"
})
export const searchBlur = () => ({
type: "search_blur"
})
- 在header的index.js文件中引入,并修改
import * as actionCreator from "./store/actionCreator";
。。。
const mapDispatchToProps = (dispatch) => {
return {
handleFocus() {
dispatch(actionCreator.searchFocus());
},
handleBlur() {
dispatch(actionCreator.searchBlur());
}
}
}
- 在header/store下创建actionType(constants)文件
export const SEARCH_FOCUS = "header/search_focus";
export const SEARCH_BLUR = "header/search_blur";
- 修改header下的actionCreator、reducer文件
//actionCreator.js
import * as actionName from "./actionType";
export const searchFocus = () => ({
type: actionName.SEARCH_FOCUS
})
export const searchBlur = () => ({
type: actionName.SEARCH_BLUR
})
//reducer
import * as actionName from "./actionType";
const defaultState = {
focused: false
}
export default (state = defaultState, action) => {
let newState = { ...state };
if (action.type == actionName.SEARCH_FOCUS) {
newState.focused = true;
return newState;
}
if (action.type == actionName.SEARCH_BLUR) {
newState.focused = false;
return newState;
}
return state;
};
- 修改header/store/index.js文件,一般情况下,不希望在header/index中直接引用header/store/actionCreator.js,希望统一由header/store/index.js暴露出去
//header/store/index.js
import reducer from "./reducer";
import * as actionCreator from "./actionCreator";
import * as actionName from "./actionType";
export { reducer, actionCreator, actionName};
header/index更改引入方式:
原来:
import * as actionCreator from "./store/actionCreator";
现在:
import { actionCreator }from "./store";
使用immutable.js插件
原因
在reducer中,我们不能对state中的代码进行修改,通常是通过拷贝一份数据然后在拷贝数据中进行修改返回,但是有的时候不小心就会修改到state中的值,又很难发现其中的问题。
因此就需要Immutable.js来进行数据的管理。这是一个第三方的库,需要进行安装
immutable的意思是:不可改变的
修改代码
npm install immutable
修改header/store/reducer.js
import * as actionName from "./actionType";
import { fromJS } from "immutable";// 导入immutable模块中fromJS方法
//使用fromJS将其变为immutable对象数据(只能使用set、get)
const defaultState = fromJS({
focused: false
})
- 在header/index.js中需要使用immutable中的get方法才能来获取immutable对象的值。
src/common/header/index.js
const mapStateToProps = (state) => {
return {
focused: state.header.get("focused")
}
}
第三步:在header/store/reducer.js中使用immutable中的set方法修改immutable对象的值。
这个方法并不会直接修改我们state中的数据,这个方法的实质是:我们使用这个方法,会结合之前的immutable对象,和我们设置的值,来返回一个全新的值
src/common/header/store/reducer.js
import * as actionName from "./actionType";
import { fromJS } from "immutable";// 导入immutable模块中fromJS方法
//使用fromJS将其变为immutable对象数据(只能使用set、get)
const defaultState = fromJS({
focused: false
})
export default (state = defaultState, action) => {
//immutable对象的set方法,会结合之前immutable对象
//和设置的值,会返回一个全新的对象
if (action.type == actionName.SEARCH_FOCUS) {
return state.set("focused",true);
}
if (action.type == actionName.SEARCH_BLUR) {
return state.set("focused", false);
}
return state;
};
使用完毕!
使用redux-immutable库
为什么使用redux-immutable
在如下代码中src/common/header/index.js中mapStateToProps里面的focused: state.header.get("focused")
这行代码,
const mapStateToProps = (state) => {
return {
focused: state.header.get("focused")
}
}
其中states 是一个js对象,而state.header是一个immutable对象,所以调用focused属性的时候,得先调用state的‘.’,再去调用header的‘.get’方法。所以说数据获取不统一,前面是按照js对象来获取,后面又是按照immutable对象来获取的,这样不太靠谱。我们希望统一一下,因此我们希望state也是一个immutable对象。
state是在最外层的总的store的reducer中生成的,引入redux-immutable,使全局state变成immutable对象
引入npm install redux-immutable
全局store的reducer
//import { combineReducers } from "redux"修改成下面这句
import { combineReducers } from "redux-immutable"
import { reducer as headReducer } from "../common/header/store"
//引入redux-immutable,使全局state变成immutable对象
const reducer= combineReducers({
header: headReducer
})
export default reducer;
因为全局state已经变为immutable对象,因此也要用get方法调用,修改header的index.js中的mapStateToProps函数
const mapStateToProps = (state) => {
return {
//focused: state.header.focused原本语句
//可以改为这样的语句:focused: state.get("header").get("focused")
focused: state.getIn(["header","focused"])//最终版本
}
}
关于immutable的使用,可以参阅其网站immutable官网文档
完毕!
增加热门搜索部分
静态
src/common/header/index.js中增加一个区域
import { HeaderWrapper, Logo, Nav, Navitem, NavSearch, Addition, Button, SearchWrapper, SearchInfo, SearchInfoTitle, SearchInfoSwitch, SearchInfoItem, SearchInfoList} from "./style";
。。。
<SearchInfo>
<SearchInfoTitle>
热门搜索
<SearchInfoSwitch>换一批</SearchInfoSwitch>
</SearchInfoTitle>
<SearchInfoList>
<SearchInfoItem>教育</SearchInfoItem>
<SearchInfoItem>教育</SearchInfoItem>
<SearchInfoItem>教育</SearchInfoItem>
<SearchInfoItem>教育</SearchInfoItem>
</SearchInfoList>
</SearchInfo>
src/common/header/style.js中增加对应的样式
export const SearchInfo = styled.div`
position: absolute;
left: 0;
top: 56px;
width: 240px;
padding: 0 20px;
box-shadow: 0 0 8px rgba(0, 0, 0, .2);
background: #fff;
`
export const SearchInfoTitle = styled.div`
margin-top: 20px;
margin-bottom: 15px;
line-height: 20px;
font-size: 14px;
color: #969696;
`;
export const SearchInfoSwitch = styled.span`
float:right;
font-size:13px;
`
export const SearchInfoList = styled.div`
overflow: hidden;
`;
export const SearchInfoItem = styled.a`
display: block;
float: left;
line-height: 20px;
padding: 0 5px;
margin-right: 10px;
margin-bottom: 15px;
font-size: 12px;
border: 1px solid #ddd;
color: #787878;
border-radius: 3px;
`
结果如下图
动态
接下来添加一个函数,并加以改造。使之做到,鼠标focus on这个搜索框的时候,才显示”热门搜索“
const getListArea = (show) => {
if (show) {
return (
<SearchInfo>
<SearchInfoTitle>
热门搜索
<SearchInfoSwitch>换一批</SearchInfoSwitch>
</SearchInfoTitle>
<SearchInfoList>
<SearchInfoItem>教育</SearchInfoItem>
<SearchInfoItem>教育</SearchInfoItem>
<SearchInfoItem>教育</SearchInfoItem>
<SearchInfoItem>教育</SearchInfoItem>
</SearchInfoList>
</SearchInfo>
)
} else {
return null;
}
}
。。。
//原来的位置调用该函数即可
{getListArea(props.focused)}
效果达成,但现在,数据是写死的,全是“教育”,接下来用AJAX从其他地方动态获取
AJAX
也就是说,需要在鼠标onFocus的时候发送ajax请求
<NavSearch
className={props.focused ? "focused" : ""}
onFocus={props.handleFocus}//发送ajax请求
onBlur={props.handleBlur}
></NavSearch>
const mapDispatchToProps = (dispatch) => {
return {
handleFocus() {//在这里发送ajax请求
dispatch(actionCreator.searchFocus());
},
handleBlur() {
dispatch(actionCreator.searchBlur());
}
}
}
一般这种异步请求,不会放在组件里面写,而是用redux-thunk放在action里面
引入依赖
(1)安装中间件 npm install redux-thunk,并配置thunk使用
(2)安装ajax数据 请求插件npm install axios
设置API,放数据
React框架寻找的特性:
1.src目录下看对应的路由
2.找不到,会到public目录下的api/headerList.json寻找 然后就会发送出来,因此我们就可以使用假数据
因此,我们在public目录下创建对应的文件
{
"success": true,
"data": ["前端","区块链","react"]
}
使用数据
第一步:在src/common/header/store/reducer中
首先,我们新创建了一个变量list。要注意的是:list中数组的类型也是immutable;因此,我们在更新数据的时候也要使得获取的数据是immutable类型的,因此我们最好在获取数据的时候将要传过去的data变为immutable对象(在actionCreators.js中)
import { constants } from './';
import { fromJS } from 'immutable';
//将其变为immutable
const defaultState = fromJS({
focused: false,
list: []
});
export default (state = defaultState, action) => {
if(action.type === constants.SEARCH_FOCUS){
return state.set('focused', true);
}
if(action.type === constants.SEARCH_BLUR){
return state.set('focused', false);
}
if(action.type === constants.CHANGE_LIST){
return state.set('list', action.data);
}
return state;
}
第二步:修改全局store,添加redux-thunk。
src/store/index.js
import { legacy_createStore as 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;
第三步:我们要在搜索框聚焦的时候,获得Ajax数据。所以在src/common/header/index.js中派发action来获取Ajax数据(所以间接使用thunk中间件)
...
const mapDispatchToProps = (dispatch) => {
return {
handleFocus() {
dispatch(actionCreator.getList());//新增语句,用来聚焦时发Ajax
dispatch(actionCreator.searchFocus());
},
handleBlur() {
dispatch(actionCreator.searchBlur());
}
}
}
...
第四步:src/common/header/store/actionCreator中定义getList方法
export const changeList = (data) => ({
type: actionName.CHANGELIST,
data: data.data
})
export const getList=()=>{
return (dispatch) => {
axios.get("/api/headerList.json").then((res) => {
console.log(res.data);
dispatch(changeList(res.data));
}).catch(() => {
console.log("error");
})
}
}
第五步:src/common/header/store/reducer中定义相应的action-state交互
import * as actionName from "./actionType";
import { fromJS } from "immutable";// 导入immutable模块中fromJS方法
//使用fromJS将其变为immutable对象数据(只能使用set、get)
const defaultState = fromJS({
focused: false,
list: []
})
export default (state = defaultState, action) => {
//immutable对象的set方法,会结合之前immutable对象
//和设置的值,会返回一个全新的对象
if (action.type == actionName.SEARCH_FOCUS) {
return state.set("focused",true);
}
if (action.type == actionName.SEARCH_BLUR) {
return state.set("focused", false);
}
if (action.type == actionName.CHANGELIST) {//交互
return state.set("list", action.data);
}
return state;
}
;
注意,到这里看上去似乎没问题了,但是,其实会有问题,immutable对象会把list:[]也变成immutable,因此也必须用immutable对象去替换,返回第四步,做如下修改:
src/common/header/store/actionCreator
import { fromJS } from "immutable";
export const changeList = (data) => ({
type: actionName.CHANGELIST,
data: fromJS(data.data)
})
第六步:修改src/common/header/index.js中的getListArea
getListArea = () => {
if (this.props.focused) {
return (
<SearchInfo>
<SearchInfoTitle>
热门搜索
<SearchInfoSwitch>换一批</SearchInfoSwitch>
</SearchInfoTitle>
<SearchInfoList>
{this.props.list.map((item) => {
return <SearchInfoItem key={ item }>{item}</SearchInfoItem>
})}
//注意,这里()=>{ return ... },这个return作何解释?我还不懂,
//没有return会报错
</SearchInfoList>
</SearchInfo>
)
} else {
return null;
}
}
结果展示:
代码优化微调
1.解构赋值
src/common/header/index.js
getListArea = () => {
const { focused, list } = this.props;
if (focused) {
return (
<SearchInfo>
<SearchInfoTitle>
热门搜索
<SearchInfoSwitch>换一批</SearchInfoSwitch>
</SearchInfoTitle>
<SearchInfoList>
{list.map((item) => {
return <SearchInfoItem key={ item }>{item}</SearchInfoItem>
})}
</SearchInfoList>
</SearchInfo>
)
} else {
return null;
}
}
class Header extends Component{
render() {
const { focused, handleFocus, handleBlur } = this.props;
return(
<HeaderWrapper>
<Logo href="/" />
。。。
)}}
2 用switch代替if
export default (state = defaultState, action) => {
//immutable对象的set方法,会结合之前immutable对象
//和设置的值,会返回一个全新的对象
switch (action.type) {
case actionName.SEARCH_FOCUS:
return state.set("focused", true);
break;//这个函数内的所有break因为上面有return,所以都不会生效,但这个习惯最好还是保留
case actionName.SEARCH_BLUR:
return state.set("focused", false);
break;
case actionName.CHANGELIST:
return state.set("list", action.data);
break;
default:
return state;
}
}
;
数据分页显示(实现换一批功能)
第一步:增加页码和总页数变量
import * as actionName from "./actionType";
import { fromJS } from "immutable";// 导入immutable模块中fromJS方法
//使用fromJS将其变为immutable对象数据(只能使用set、get)
const defaultState = fromJS({
focused: false,
list: [],
page: 1,
totalPage:1
})
export default (state = defaultState, action) => {
//immutable对象的set方法,会结合之前immutable对象
//和设置的值,会返回一个全新的对象
switch (action.type) {
case actionName.SEARCH_FOCUS:
return state.set("focused", true);
break;
case actionName.SEARCH_BLUR:
return state.set("focused", false);
break;
case actionName.CHANGELIST:
return state.set("list", action.data).set("totalPage",action.totalPage);
break;
default:
return state;
}
};
修改changeList行为
export const changeList = (data) => ({
type: actionName.CHANGELIST,
data: fromJS(data),
totalPage: Math.ceil(data.length / 10)//10组数据一页
})
export const getList=()=>{
return (dispatch) => {
axios.get("/api/headerList.json").then((res) => {
dispatch(changeList(res.data.data));
}).catch(() => {
console.log("error");
})
}
}
第二步拿到当前页码
const mapStateToProps = (state) => {
return {
focused: state.getIn(["header", "focused"]),
list: state.getIn(["header", "list"]),
page:state.getIn(["header","page"])
}
}
修改getListArea的内容,获取到当前page并且将显示范围限制在第一页
由于list为immutable对象,可以将它转换成JS语句,以便修改
获取到当前page
getListArea = () => {
const { focused, list, page } = this.props;
const newList = list.toJS();//list是个immutable对象,这一语句可以将它转换成JS语句
const pageList = [];//每次一调用,就会重置该数组
for (let i = (page - 1) * 10; i < page * 10; i++){
pageList.push(
<SearchInfoItem key={newList[i]}>{newList[i]}</SearchInfoItem>
)
}
if (focused) {
return (
<SearchInfo>
<SearchInfoTitle>
热门搜索
<SearchInfoSwitch>换一批</SearchInfoSwitch>
</SearchInfoTitle>
<SearchInfoList>
{pageList}
</SearchInfoList>
</SearchInfo>
)
} else {
return null;
}
}
第三步:热门搜索块实现失焦后仍不消失,但在鼠标移出后必定消失的功能
新增变量 mouseIn,热门搜索块的出现与否,应由 mouseIn和focused共同决定
const defaultState = fromJS({
focused: false,
mouseIn:false,
list: [],
page: 1,
totalPage:1
})
const mapStateToProps = (state) => {
return {
focused: state.getIn(["header", "focused"]),
list: state.getIn(["header", "list"]),
page:state.getIn(["header","page"]),
mouseIn: state.getIn(["header","mouseIn"])
}
}
定义两个函数,负责改变mouseIn的状态
getListArea = () => {
const { focused, list, page, mouseIn, handleMouseEnter, handleMouseLeave } = this.props;
const newList = list.toJS();
const pageList = [];
for (let i = (page - 1) * 10; i < page * 10; i++){
pageList.push(
<SearchInfoItem key={newList[i]}>{newList[i]}</SearchInfoItem>
)
}
if (focused || mouseIn) {//热门搜索块的出现与否,由mouseIn和focused共同决定
return (
<SearchInfo
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
<SearchInfoTitle>
热门搜索
<SearchInfoSwitch>换一批</SearchInfoSwitch>
</SearchInfoTitle>
<SearchInfoList>
{pageList}
</SearchInfoList>
</SearchInfo>
)
} else {
return null;
}
}
const mapDispatchToProps = (dispatch) => {
return {
。。。
handleMouseEnter() {
dispatch(actionCreator.mouseEnter());
},
handleMouseLeave() {
dispatch(actionCreator.mouseLeave());
}
}
}
export default (state = defaultState, action) => {
switch (action.type) {
。。。
case actionName.MOUSE_ENTER:
return state.set("mouseIn", true);
break;
case actionName.MOUSE_LEAVE:
return state.set("mouseIn", false);
break;
default:
return state;
}
}
完成热门搜索块的出现与消失
第四步:定义换页函数
我的盲点: 函数如果想传参数,要写成箭头函数的形式
src/common/header/index
<SearchInfoSwitch onClick={() => { handleChangePage(page,totalPage) } }>换一批</SearchInfoSwitch>
src/common/header/index
handleChangePage(page, totalPage) {
if (page < totalPage) {
dispatch(actionCreator.changePage(page+1));
} else {
dispatch(actionCreator.changePage(1));//回到第一页
}
}
src/common/header/store/actionCreator
export const changePage = (newPage) => ({
type: actionName.CHANGEPAGE,
newPage
})
src/common/header/store/reducer
case actionName.CHANGEPAGE:
return state.set("page", action.newPage);
break;
最后一点
我们实现代码之后,每次打开网页都会报一个警告
原因是什么?
const { focused, list, page, totalPage, mouseIn, handleMouseEnter, handleMouseLeave, handleChangePage } = this.props;
const newList = list.toJS();//一开始这是一个空数组
const pageList = [];
for (let i = (page - 1) * 10; i < page * 10; i++){
console.log(newList[i])//所以这里会发现,输出undefined
pageList.push(
<SearchInfoItem key={newList[i]}>{newList[i]}</SearchInfoItem>
)
}
当我们加载网页,它就会渲染我们的网页,但是此时,我们没有获得AJAX数据,也就是说,我们的list中是没有值的,进而就导致key=undefined 就会报错。
解决的方法很简单,如下:当我们list有值的时候,我们的newList也有值,我们通过看newList.length它的长度来判断是否有值,只有存在值时,我们才会运行之后的for循环,这样就不会报错了
if (newList.length){
for (let i = (page - 1) * 10; i < page * 10; i++){
pageList.push(
<SearchInfoItem key={newList[i]}>{newList[i]}</SearchInfoItem>
)
}
}
避免无意义的Ajax请求
当我们聚焦搜索框的时候,会发送AJAX的数据请求来获取数据,当我们关闭了,再打开,它会再次发送数据,实际上第一次聚焦的时候就已经把所有的数据都请求回来了,所以:
我们只需要获取一次数据就够了。
<NavSearch
className={focused ? "focused" : ""}
onFocus={() => { handleFocus(list) }}
onBlur={handleBlur}
></NavSearch>
....
handleFocus(list) {
if (list.size===0) {
dispatch(actionCreator.getList());
}
dispatch(actionCreator.searchFocus());
},
末尾修改一下换一批的cursor
export const SearchInfoSwitch = styled.span`
cursor:pointer;//手形指针
float:right;
font-size:13px;
`