简书Header组件开发(热门搜索换页功能、换页旋转动画效果)

涉及知识点总结

热门搜索换页功能知识点:

  • 想从state中获取数据就到connect的参数1mapStateToProps()中设置映射,再从this.props上去拿
  • 在组件上绑定事件函数要注意事件名称是驼峰形式的
  • 函数如果要处理state中的数据就要通过action
  • 需要涉及action操作的函数都放在connect的参数2mapDispatchProps()中进行定义
  • (connect来自react-redux中间件)

换页旋转功能知识点:

  • 在设置动画时可以使用**ref获取对应的DOM元素**进行操作
  • js中的react 样式是字符串类型的,所以在组件中设置DOM元素样式时属性值需要引号
  • 想要获取 样式属性值 中的数字,可使用字符串的relpace()结合正则表达式将所有非数字部分替换为空,则剩余的就是数字,但注意:得到的数字是字符串类型的
  • ParseInt()将字符串=>数字,空字符串=>Nan,Nan+数字=Nan
  • 简单的 旋转,缩放,倾斜或平移效果可以使用transform样式属性来完成,transform-origin属性可设置中心点,动画效果可以使用transition属性来实现。
  • 注意
    • transition仅对block元素生效,需要使用display:block;**
    • block会使iconfont和“换一批”分成上下两行,所以需要float:left使iconfont脱离文档流左浮动以出现在文字左边

热门搜索换页功能

现在我们的热门搜索框内数据是全部显示的:
当前效果
实现效果:每次只显示10个标签,点击“换一批”后显示另一批标签,也就是1页存放10个标签,点击“换一批”则下一页,到最后一页后又返回第一页。

每页显示10个标签

思路:

  1. 在header组件的store中添加两个数据page(当前所在页)和totalPage(总页数)
  2. 在获取list的action函数中改变totalPage的值。
  3. actionCreators中获取标签数据后进行分页再传给reducer
  4. reducer拿到totalPage数据后添加到state中
  5. 在index.js中获取page,根据page显示list数据

store中添加两个数据

**header-store-reducer.js中添加两个数据pagetotalPage**:

1
2
3
4
5
6
const defaultState = fromJS({
focused: false,
list: [],
page: 1,
totalPage: 1
});

获取数据后进行分页

我们在index中使用actionCreators.js中的getList()来获取热门标签数据,而获取到的数据是通过changeList()传给reducer的,所以分页处理也放到changeList()中完成即可。

header-store-actionCreators.js:

1
2
3
4
5
const changeList = (data) => ({
type: constants.CHANG_LIST,
data: fromJS(data),
totalPage: Math.ceil(data.length / 10)
});

reducer将totalPage添加到state

reducer拿到totalPage数据后添加到state中。

header-store-reducer.js:
totalPage数据后添加到state中

redux效果:
redux效果
可以通过redux看到state中数据(当前所在页page和总页数totalPage)的变化。


组件中根据page显示list数据

index.js中,我们原本将整个list循环显示在页面上,但现在我们应先获取page,根据page显示list数据

header-index.js:
1.在connect的参数1mapStateToProps()中**获取到state的page**:

1
2
3
4
5
6
7
const mapStateToProps = (state) => {
return {
focused: state.getIn(["header", "focused"]),
list: state.getIn(["header", "list"]),
page: state.getIn(["header", "page"])
}
}

2.根据page循环10个标签
header-index.js

页面效果:
页面效果
一页只显示10个标签,但鼠标点击“热门搜索框”时搜索框消失。

immutable数组的toJS()

  • immutable数组是不可改变的,增删查改都比较麻烦,所以我们可以通过toJS()将immutable数组转换为JS数组
  • 语法:const jsList = toJS(immutable数组);

设置“热门搜索”显示条件

实现效果:鼠标停在“热门搜索”内时搜索框会一直显示。

思路:

  1. 在state中添加mouseIn数据
  2. 当鼠标移入“热门搜索”中时,mouseIn变为true:也就是给SearchInfo组件绑定onMouseEnter事件函数,触发时通过action改变state中mouseIn的值。
  3. 鼠标移入热门搜索时mouseIn变为true,移出时要变回false,所以同2的流程处理一下。
  4. 原本“热门搜索”显示条件为focused,现在要增加一个“或者mouseIn”,我们要让她符合一个就能显示

state中添加mouseIn数据

header-store-reducer.js:

1
2
3
4
5
6
7
const defaultState = fromJS({
focused: false,
mouseIn: false,
list: [],
page: 1,
totalPage: 1,
});

onMouseEnter事件改变mouseIn

  • 当鼠标移入“热门搜索”中时,mouseIn变为true:也就是给SearchInfo组件绑定onMouseEnter事件函数,触发时通过action改变state中mouseIn的值。
  • 要想改变mouseIn就要通过action

1.在SearchInfo组件上绑定onMouseEnter事件函数handleMouseEnter

1
2
3
4
5
6
7
<SearchInfo onMouseEnter={handleMouseEnter}>
<SearchInfoTitle>
热门搜索
<SearchInfoSwitch>换一批</SearchInfoSwitch>
</SearchInfoTitle>
<SearchItemList>{pageList}</SearchItemList>
</SearchInfo>

2.在mapDispatchProps()中定义函数handleMouseEnter,通过action(即mouseEnter)改变mouseIn:

1
2
3
handleMouseEnter(){
dispatch(actionCreators.mouseEnter())
}

3.在actionCreators.js中创建mouseEnter():

1
2
3
export const mouseEnter = () => ({
type: constants.MOUSE_ENTER
});

4.在reducer.js中处理action:
在reducer.js中处理action

验证redux:
验证redux


onMouseLeave事件改变mouseIn

  • 鼠标移入热门搜索时mouseIn变为true,移出时要变回false,所以同样的流程处理一下。
  • 一样,要想改变mouseIn就要通过action
  1. 在SearchInfo组件上绑定onMouseLeave事件函数handleMouseLeave
  2. 在mapDispatchProps()中定义函数handleMouseLeave,通过action(即mouseLeave)改变mouseIn
  3. 在actionCreators.js中创建mouseLeave()
  4. 在reducer.js中处理action

验证:
同样使用redux进行验证,鼠标移入“热门搜索”时mouseIn变为true,移出时mouseIn变为false。


改变“热门搜索”显示条件

原本“热门搜索”显示条件为focused,现在要增加一个“或者mouseIn”,我们要让她符合一个就能显示

store中引入mouseIn:

1
2
3
4
5
6
7
8
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作为判断条件:
增加一个mouseIn作为判断条件


点击“换一批”换页

引入totalPage,给SearchInfoSwitch组件绑定onClick事件handleChangePage,执行handleChangePage函数时传递page和totalPage,在handleChangePage函数中进行判断后决定action(即将被使用的page)的值。

index.js:

1
2
3
4
<SearchInfoSwitch
onClick={() => { handleChangePage(totalPage, page) }}>
换一批
</SearchInfoSwitch>

index.js

actionCreator.js:

1
2
3
4
export const changePage = (page) => ({
type: constants.CHANGE_PAGE,
page
});

reducer.js:
reducer.js


解决key值报错

虽然我们在循环中给每一项都设置了key值:

1
2
3
4
5
for (let i = (page - 1) * 10; i < page * 10; i++) {
pageList.push(
<SearchInfoItem key={newList[i]}>{newList[i]}</SearchInfoItem>
)
}

但还是报错了:
key值报错

原因:
原因
通过打印可以看到前面10次循环的key值都是undefined,因为初始化页面时render函数就执行了,带动getListArea()执行了for循环,但list中数据是通过AJAX获取的,而我们的AJAX请求时点击搜索框才发出的,所以初始化时list还是空数组,也就拿不到数组元素了

解决方法:
list拿到数据时才进行循环即可:

1
2
3
4
5
6
7
if (newList.length) {
for (let i = (page - 1) * 10; i < page * 10; i++) {
pageList.push(
<SearchInfoItem key={newList[i]}>{newList[i]}</SearchInfoItem>
)
}
}

优化:merge()代替多次set()

  • 使用多次set()修改immutable数据时可使用merge()代替来合成多次修改后的immutable对象。

reducer中:

1
2
3
4
5
6
state.set("list", action.data).set("totalPage", action.totalPage);
//改为
state.merge({
list: action.data,
totalPage: action.totalPage
})

换页旋转动画效果

实现效果:点击“换一批”后,图标旋转
换页旋转动画效果

思路:

  1. iconfont中找到spin图标放到相应位置并设置样式
  2. 使用ref获取到spin图标的DOM元素
  3. 点击“换一批”时触发的事件绑定函数中,使用**transform样式属性使每次点击spin图标就增加360度来旋转**
    • 设置**transform-origin属性让图标围绕中心点旋转**。
    • 由于度数需要每次增加360度,而点击时设置样式属性为360度则是写死了,无法递增。所以我们每次点击都需要获取当前transform样式属性值中的度数作为原始度数,在原始度数基础上加360度作为transform样式属性值
    • 原始度数的获取:首先明确js中样式是字符串形式的,所以可以使用字符串的replace()替换属性值中所有非数字字符串为空,这样剩下的就是数字(即度数)。
    • 注意:
      1. 获取到的原始度数是字符串类型的,使用ParseInt转换为数字后再进行加操作。
      2. 一开始是0度,替换字符串后得到空字符串,ParseInt(空字符串)=Nan,Nan+360=Nan,如果这样第一次设置旋转角度就会失败,所以要加个判断,让原始度数是空字符串时转换为数字0
  4. 在spin图标的样式中使用**transition属性使旋转产生动画效果**
    • 注意
    • transition仅对block元素生效,需要使用display:block;**
    • block会使iconfont和“换一批”分成上下两行,所以需要float:left使iconfont脱离文档流左浮动以出现在文字左边

iconfont下载、替换本地图标

搜索“spin”,添加到项目后下载到本地,解压后拷贝所需的5个文件到static中的iconfont文件夹中:
所需的5个文件

将文件夹中的iconfont.css内容拷贝到static-iconfont-iconfont.js中,一点点进行替换(记得@font-face中,只要不是data开头的路径前都要加./将其改为相对路径)


使用spin图标、设置样式

1.index.js中使用spin图标:

1
2
3
4
5
<SearchInfoSwitch
onClick={() => { handleChangePage(totalPage, page) }}>
<span className="iconfont">&#xe851;</span>
换一批
</SearchInfoSwitch>

结果图标出现在右下角
效果
原因:因为在style中我们设置了:SearchWrapper组件下的iconfont都相对SearchWrapper进行绝对定位在右下角。

解决方法:
style.js中修改
index.js中修改

效果

2.设置spin图标样式
index.js中给spin图标增加样式spin:

1
2
3
4
5
<SearchInfoSwitch
onClick={() => { handleChangePage(totalPage, page) }}>
<span className="iconfont spin">&#xe851;</span>
换一批
</SearchInfoSwitch>

style.js中:

1
2
3
4
5
6
7
8
export const SearchInfoSwitch = styled.div`
float:right;
font-size: 13px;
.spin{
font-size: 12px;
margin-right: 2px;
}
`;

效果展示


点击旋转360度

  1. 使用**ref获取到spin图标的DOM元素**,传入点击事件函数handleChangePage中
    步骤1
  2. handleChangePage函数拿到spin以后往spin的样式中添加transform属性 使每次点击spin图标就增加360度来旋转
    • 设置**transform-origin属性让图标围绕中心点旋转**。
    • 由于度数需要每次增加360度,而点击时设置样式属性为360度则是写死了,无法递增。所以我们每次点击都需要获取当前transform样式属性值中的度数作为原始度数,在原始度数基础上加360度作为transform样式属性值
    • 原始度数的获取:首先明确js中样式是字符串形式的,所以可以使用字符串的replace()替换属性值中所有非数字字符串为空,这样剩下的就是数字(即度数)。
    • 注意:
      1. 获取到的原始度数是字符串类型的,使用ParseInt转换为数字后再进行加操作。
      2. 一开始是0度,替换字符串后得到空字符串,ParseInt(空字符串)=Nan,Nan+360=Nan,如果这样第一次设置旋转角度就会失败,所以要加个判断,让原始度数是空字符串时转换为数字0

步骤2

结果
结果
通过打印spin可以看到点击以后旋转角度有所变化,但看不到效果,所以接下来要设置动画效果。


动画效果

  • 在spin图标的样式中使用**transition属性使旋转产生动画效果**
    • 注意
    • transition仅对block元素生效,需要使用display:block;**
    • block会使iconfont和“换一批”分成上下两行,所以需要float:left使iconfont脱离文档流左浮动以出现在文字左边
      添加样式
,