react实战搭建简书网站,持续更新

### 创建简书项目

- yarn add create-react-app 在控制台安装脚手架工具
- create-react-app jianshu 在控制台使用脚手架创建jianshu项目
- cd jianshu 进入项目文件夹

### 添加样式管理模块

- yarn add styled-components 安装样式管理模块依赖
- 样式管理模块
- 将css文件改写成js
- 创建style.js文件
- 引入createGlobalStyle import { createGlobalStyle } from 'styled-components'
- 添加全局rest样式
- 方便在需要使用地方使用
 1 import { createGlobalStyle } from 'styled-components'
 2 export const GlobalStyle = createGlobalStyle`
 3 html, body, div, span, applet, object, iframe,
 4 h1, h2, h3, h4, h5, h6, p, blockquote, pre,
 5 a, abbr, acronym, address, big, cite, code,
 6 del, dfn, em, img, ins, kbd, q, s, samp,
 7 small, strike, strong, sub, sup, tt, var,
 8 b, u, i, center,
 9 dl, dt, dd, ol, ul, li,
10 fieldset, form, label, legend,
11 table, caption, tbody, tfoot, thead, tr, th, td,
12 article, aside, canvas, details, embed,
13 figure, figcaption, footer, header, hgroup,
14 menu, nav, output, ruby, section, summary,
15 time, mark, audio, video{
16 margin: 0;
17 padding: 0;
18 border: 0;
19 font-size: 100%;
20 font: inherit;
21 font-weight: normal;
22 vertical-align: baseline;
23 }
24 /* HTML5 display-role reset for older browsers */
25 article, aside, details, figcaption, figure,
26 footer, header, hgroup, menu, nav, section{
27 display: block;
28 }
29 ol, ul, li{
30 list-style: none;
31 }
32 blockquote, q{
33 quotes: none;
34 }
35 blockquote:before, blockquote:after,
36 q:before, q:after{
37 content: '';
38 content: none;
39 }
40 table{
41 border-collapse: collapse;
42 border-spacing: 0;
43 }
44  
45 /* custom */
46 a{
47 color: #7e8c8d;
48 text-decoration: none;
49 -webkit-backface-visibility: hidden;
50 }
51 ::-webkit-scrollbar{
52 width: 5px;
53 height: 5px;
54 }
55 ::-webkit-scrollbar-track-piece{
56  
57 -webkit-border-radius: 6px;
58 }
59 ::-webkit-scrollbar-thumb:vertical{
60 height: 5px;
61  
62 -webkit-border-radius: 6px;
63 }
64 ::-webkit-scrollbar-thumb:horizontal{
65 width: 5px;
66  
67 -webkit-border-radius: 6px;
68 }
69 html, body{
70 width: 100%;
71 font-family: "Arial", "Microsoft YaHei", "黑体", "宋体", "微软雅黑", sans-serif;
72 }
73 body{
74 line-height: 1;
75 -webkit-text-size-adjust: none;
76 -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
77 }
78 html{
79 overflow-y: scroll;
80 }
81  
82 /*清除浮动*/
83 .clearfix:before,
84 .clearfix:after{
85 content: " ";
86 display: inline-block;
87 height: 0;
88 clear: both;
89 visibility: hidden;
90 }
91 .clearfix{
92 *zoom: 1;
93 }
94  
95 /*隐藏*/
96 .dn{
97 display: none;
98 }
99 `
- 使用创建style.js文件的公共样式
- 在需要使用style.js的组件引入GlobalStyle

1 import React from 'react'; 2 import {GlobalStyle} from './style.js' 3 function App() { 4 return ( 5 <div className="App"> 6 <GlobalStyle/> 7 <h1>简书</h1> 8 </div> 9 ); 10 } 11 12 export default App;

### 头部区块的编写

- 头部我们可以抽离出一个公共组件,各个页面均可使用
- 在src目录下创建一个common文件夹来放置所有的公共组件
- 在common文件夹中创建一个header文件夹,放置头部组件
- 在header文件夹中创建index.js和style.js文件
- 在src文件夹中创建一个static文件夹,用来存放静态资源,在简书首页中通过调试工具拿到头部logo图片存放在static中
- 使用styled-components中的styled对象书写头部整体的样式,和logo单独的样式。详情请看代码
- 在App.js中引入header组件

 

#### header头部组件代码
 1 /**
 2 * 头部公共组件 index文件
 3 */
 4 import React, { Component } from 'react'
 5 import {
 6 HeaderWrapper,
 7 Logo
 8 } from './style'
 9 class Header extends Component {
10 render() {
11 return (
12 <HeaderWrapper>
13 <Logo/>
14 </HeaderWrapper>
15 )
16 }
17 }
18 export default Header
19 
20 // --------------------------分割线---------------------
21 /**
22 * 头部公共组件 style文件
23 */
24 import styled from 'styled-components'
25 import logo from '../../static/logo.png'
26 
27 export const HeaderWrapper = styled.div`
28 position: reletive;
29 height: 58px;
30 border-bottom: 1px solid #f0f0f0;
31 `
32 export const Logo = styled.a`
33 position: absolute
34 display:block;
35 height: 56px;
36 width:100px;
37 background: url(${logo});
38 background-size: contain;
39 `
40 // --------------------------分割线---------------------
41 /**
42 * 在App.js中的引用
43 */
44 import React from 'react';
45 import {GlobalStyle} from './style.js'
46 import Header from './common/header'
47 function App() {
48 return (
49 <div className="App">
50 <GlobalStyle/>
51 <Header></Header>
52 </div>
53 );
54 }
55 
56 export default App;
#### 头部导航初步实现
- 添加了导航组件,以及他的子组件。
- 里面包含了styled-components的一些样式写法
- 同一个组件样式样式不同可以通过类型来区别,具体查看代码
- &::placeholder { color: #999} 表示这个input的placeholder的字体颜色
- &.left 表示含有left这个类型的组件所具有的样式
/**
* index.js文件
*/
/**
* 头部公共组件
*/
import React, { Component } from 'react'
import {
HeaderWrapper,
Logo,
Nav,
NavItem,
NavSearch,
Addition,
Button
} from './style'
class Header extends Component {
render() {
return (
<HeaderWrapper>
<Logo/>
<Nav>
<NavItem className='left active'>首页</NavItem>
<NavItem className='left'>下载APP</NavItem>
<NavSearch/>
<NavItem className='right'>Aa</NavItem>
<NavItem className='right'>登陆</NavItem>
</Nav>
<Addition>
<Button>注册</Button>
<Button className='writting'>写文章</Button>
</Addition>
</HeaderWrapper>
)
}
}
export default Header


// --------------------------分割线---------------------
/**
* style.js文件
*/
import styled from 'styled-components'
import logo from '../../static/logo.png'


export const HeaderWrapper = styled.div`
position: reletive;
height: 58px;
border-bottom: 1px solid #f0f0f0;
`
export const Logo = styled.a`
position: absolute
display:block;
height: 56px;
width:100px;
background: url(${logo});
background-size: contain;
`
export const Nav = styled.div`
width: 960px;
padding-right: 70px;
box-sizing: border-box;
height: 100%
margin: 0 auto;
`
export const NavItem = styled.div`
line-height: 56px;
padding: 0 15px;
font-size: 17px;
color: #333;
cursor:pointer;
&.left {
float: left;
}
&.right {
float: right;
color: #969696
}
&.active {
color: #ea6f5a
}
`
export const NavSearch = styled.input.attrs({
placeholder: '搜索'
})`
width: 160px;
height: 38px;
margin: 9px 10px 9px 15px;
background: #eee;
border:none;
outline:none;
padding: 0 20px;
box-sizing: border-box;
border-radius: 19px;
font-size: 14px;
&::placeholder {
color: #999
}
`
export const Addition = styled.div`
position: absolute;
right: 0;
top: 0;
height: 56px;
`
export const Button = styled.div`
cursor: pointer;
float: left;
width: 98px;
height: 38px;
box-sizing: border-box;
border: 1px solid #ea6f5a
padding: 5px 11px;
margin-top: 9px;
margin-right: 20px;
line-height: 26px;
text-align: center;
border-radius: 18px;
font-size: 14px;
color: #ea6f5a;
&.writting{
background: #ea6f5a;
color: #fff;
}
`
#### 使用icofont.cn库实现小图标
- 选择自己需要的图标添加到项目中
- 然后下载到本地
- 解压后将iconfont.eot,iconfont.css,iconfont.svg,iconfont.ttf,iconfont.woff,iconfont.woff2文件拷贝到static文件夹里面
- 将iconfont.css文件修改成如下,并且将后缀修改成.js。注意这里src中url是有做修改的,url中含有iconfont的添加了./
- 并在App.js文件中引入并使用,让他能够全局使用。
- 参考样式管理模块。
- 引入以后在需要使用的地方添加<i className='iconfont'>&#xe636;</i>
- i标签中的内容是你图标的名字,可以在你iconfont.cn的仓库中找到。其他是固定写法
 1 import { createGlobalStyle } from 'styled-components'
 2 export const IconfontStyle = createGlobalStyle`
 3 
 4 
 5 @font-face {
 6 font-family: "iconfont";
 7 src: url('./iconfont.eot?t=1568016326268'); /* IE9 */
 8 src: url('./iconfont.eot?t=1568016326268#iefix') format('embedded-opentype'), /* IE6-IE8 */
 9 url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAAO8AAsAAAAAB8gAAANvAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCDHAqDDIJrATYCJAMQCwoABCAFhG0HPxvUBsgeh7HzJIRiiFfflbSMh69h7v3dvbtNq6qoUuv4qMSXBt0hLAqMxDgYjzAI04VmcphTzzSQx6Aigwx1EqeKxoA/QP7tSdAAAVNafZ7L6bX8AFvfajuuQWPTGIOPegHGWwPbE9sgARdIgBpzw9gFLfE4gXZjfHHn6bml0ElhjgvEK11tgk4Zs9JQHKrQrFhaxGeQ1PSR/w58Cj4f/2C1TiSNzJx6cZ/WCok/XT+HC5P/T4ZajwDldE6wVWTMA4V4rHRfE6LYPEG72lu0CVRVYqoOn8/h//8j1VEM1frLIwmZaDILO8E6imd+ujoCEj99BAQ/wwtk+CCjK9rxHviCWCO3SorS09nVanXUOiSPezy/9OHUskfvKoiP8uR55cP55Y+nVTxZUHidqePmPvRklS2j9j3hHj8eXcqhKxn2WuuO1LKKwvKSVXciLj1y2mxF5V1K0Ti5qnrlMLR+QGXTDXkeQHu0f0Nz73NTKiqmBOm5Zcd3w+QzpeUPOG5WQB9opE3S0ZGSzAwQR2uFhaMktdFiwKz1VnUYqVqHqaOfACFT3ZMGjiquzMzIyCqpHTUwyZ3LbG503Vaz1iUiwmXtgYPrdksX21++unimuLDn2bM9+d7327Oy+k+AmwMteW9jM99aevUO1TO/0dICjU9taS/9Lvknjxt/PzaYi0oNXuZTa3Zr7VKYFtevtX75n8H9/YZ4uHpisAM8TxP+5wmYs1z4LWiJz/m1MOjPfcfBFz9o6B77tcoF/FjveRl6n6eJhilfv7Kz8j8pO1YUTW1aSlE0jkL3FMupcRLatePvpOj3egrTUOdCQjWUIGkZRVaN0wprHg0d1tFUHaHdnIzVHQZQgihtmDUDIPQ6hKTbV2S9btEK6xUahn1BU28C2l1G7y07TKajFEqaGGnFzWOw3mLaFNvGUFhcTNQRliaaVgfsckIdehAH+QfmkxnERugUMxwj1WDGFKxQ04rTwWnEYjGxnZptpIX5a4zZowMClKon+beYVkDOUEQThmiFNRsD07Uw2ShebQYV3l+MUI1g0YQ2UKry5QjKQe8eC+IvsAMtw2DrRLmVcxxGUgVjGAWmoExWWDpwEhYrZsLs1YPaEC0Yf9qAiF20AFRN6WrwX95kfcN10M6cUyJFjhINaT1IPvu+DsICg0Ii+KQmAAA=') format('woff2'),
10 url('./iconfont.woff?t=1568016326268') format('woff'),
11 url('./iconfont.ttf?t=1568016326268') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */
12 url('./iconfont.svg?t=1568016326268#iconfont') format('svg'); /* iOS 4.1- */
13 }
14 
15 
16 .iconfont {
17 font-family: "iconfont" !important;
18 font-size: 16px;
19 font-style: normal;
20 -webkit-font-smoothing: antialiased;
21 -moz-osx-font-smoothing: grayscale;
22 }
23 
24 
25 .icon41:before {
26 content: "\e62b";
27 }
28 
29 
30 .iconiconset0137:before {
31 content: "\e624";
32 }
33 
34 
35 .iconAa:before {
36 content: "\e636";
37 }
38 `
#### 添加头部的动画效果
- 将头部动画样式写好以后
- 在input框绑定失去和获得焦点事件
- 在state中定义一个变量focused来控制样式的变化
- 通过事件的触发,来改变focused的值具体代码如下
  1 /**
  2 * 头部公共组件
  3 */
  4 import React, { Component } from 'react'
  5 import {
  6 HeaderWrapper,
  7 Logo,
  8 Nav,
  9 NavItem,
 10 NavSearch,
 11 Addition,
 12 Button,
 13 SearchBox
 14 } from './style'
 15 class Header extends Component {
 16 constructor(props){
 17 super(props);
 18 this.state = {
 19 focused: false
 20 }
 21 this.handleInputFocus = this.handleInputFocus.bind(this);
 22 this.handleInputBlur = this.handleInputBlur.bind(this);
 23 }
 24 render() {
 25 return (
 26 <HeaderWrapper>
 27 <Logo/>
 28 <Nav>
 29 <NavItem className='left active'>首页</NavItem>
 30 <NavItem className='left'>下载APP</NavItem>
 31 <SearchBox>
 32 <NavSearch
 33 onFocus = {this.handleInputFocus}
 34 onBlur = {this.handleInputBlur}
 35 className = {this.state.focused ? 'focused' : ''}
 36 />
 37 <i className = {this.state.focused ? 'focused iconfont' : 'iconfont'}>&#xe62b;</i>
 38 </SearchBox>
 39 <NavItem className='right icon'>
 40 <i className='iconfont'>&#xe636;</i>
 41 </NavItem>
 42 <NavItem className='right'>登陆</NavItem>
 43 </Nav>
 44 <Addition>
 45 <Button>注册</Button>
 46 <Button className='writting'>
 47 <i className='iconfont'>&#xe624;</i>
 48 写文章
 49 </Button>
 50 </Addition>
 51 </HeaderWrapper>
 52 )
 53 }
 54 // 失去焦点
 55 handleInputBlur() {
 56 this.setState({
 57 focused: false
 58 })
 59 }
 60 // 获得焦点
 61 handleInputFocus() {
 62 this.setState({
 63 focused: true
 64 })
 65 }
 66 }
 67 export default Header
 68 
 69 
 70 // --------------------------分割线---------------------
 71 /**
 72 * 样式
 73 */
 74 import styled from 'styled-components'
 75 import logo from '../../static/logo.png'
 76 
 77 
 78 export const HeaderWrapper = styled.div`
 79 position: relative;
 80 height: 58px;
 81 border-bottom: 1px solid #f0f0f0;
 82 `
 83 export const Logo = styled.a`
 84 position: absolute
 85 display:block;
 86 height: 56px;
 87 width:100px;
 88 background: url(${logo});
 89 background-size: contain;
 90 `
 91 export const Nav = styled.div`
 92 width: 960px;
 93 padding-right: 70px;
 94 box-sizing: border-box;
 95 height: 100%
 96 margin: 0 auto;
 97 `
 98 export const NavItem = styled.div`
 99 line-height: 56px;
100 padding: 0 15px;
101 font-size: 17px;
102 color: #333;
103 cursor:pointer;
104 &.icon {
105 .iconfont {
106 display:block;
107 width: 24px;
108 height: 24px;
109 }
110 }
111 &.left {
112 float: left;
113 }
114 &.right {
115 float: right;
116 color: #969696
117 }
118 &.active {
119 color: #ea6f5a
120 }
121 `
122 export const SearchBox = styled.div`
123 position: relative;
124 float: left;
125 .iconfont {
126 position: absolute;
127 right: 15px;
128 top: 13px;
129 line-height: 30px;
130 text-align: center;
131 height: 30px;
132 width: 30px;
133 border-radius: 50%;
134 cursor: pointer;
135 transition: background 1s;
136 -moz-transition: background 1s; /* Firefox 4 */
137 -webkit-transition: background 1s; /* Safari 和 Chrome */
138 -o-transition: background 1s; /* Opera */
139 &.focused{
140 background: #777;
141 transition: background 1s;
142 -moz-transition: background 1s; /* Firefox 4 */
143 -webkit-transition: background 1s; /* Safari 和 Chrome */
144 -o-transition: background 1s; /* Opera */
145 }
146 }
147 `
148 export const NavSearch = styled.input.attrs({
149 placeholder: '搜索'
150 })`
151 width: 180px;
152 height: 38px;
153 margin: 9px 10px 9px 15px;
154 background: #eee;
155 border:none;
156 outline:none;
157 padding: 0 40px 0 20px;
158 border-radius: 19px;
159 font-size: 14px;
160 transition: width 1s;
161 -moz-transition: width 1s; /* Firefox 4 */
162 -webkit-transition: width 1s; /* Safari 和 Chrome */
163 -o-transition: width 1s; /* Opera */
164 &::placeholder {
165 color: #999
166 }
167 &.focused {
168 width: 240px;
169 transition: width 1s;
170 -moz-transition: width 1s; /* Firefox 4 */
171 -webkit-transition: width 1s; /* Safari 和 Chrome */
172 -o-transition: width 1s; /* Opera */
173 }
174 `
175 export const Addition = styled.div`
176 position: absolute;
177 right: 0;
178 top: 0;
179 height: 56px;
180 `
181 export const Button = styled.div`
182 cursor: pointer;
183 float: left;
184 width: 98px;
185 height: 38px;
186 box-sizing: border-box;
187 border: 1px solid #ea6f5a
188 padding: 5px 11px;
189 margin-top: 9px;
190 margin-right: 20px;
191 line-height: 26px;
192 text-align: center;
193 border-radius: 18px;
194 font-size: 14px;
195 color: #ea6f5a;
196 &.writting{
197 background: #ea6f5a;
198 color: #fff;
199 .iconfont {
200 display: inline-block;
201 width:19px;
202 height:19px;
203 }
204 }
205 `
### 使用redux管理数据
- 安装redux,react-redux
- 在src目录创建store文件夹,分别创建index.js,reducer.js文件
- App.js引入Provider,绑定store为最顶层props
- header文件夹中index.js文件,引入connect,创建mapStateToprops, mapDispathTpprops函数获取store数据,派发action
- 代码如下
  1 /**
  2 * index.js
  3 */
  4 import { createStore, compose } from 'redux';
  5 import reducer from './reducer'
  6 // devtools配置
  7 const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
  8 const store = createStore(reducer, composeEnhancers());
  9 
 10 
 11 export default store;
 12 
 13 
 14 // --------------------------分割线---------------------
 15 const defaultState = {
 16 focused: false
 17 };
 18 
 19 
 20 export default (state=defaultState, action) => {
 21 if(action.type === 'search_focus'){ // input获取光标事件触发的action
 22 const newState = JSON.parse(JSON.stringify(state))
 23 newState.focused = true;
 24 return newState
 25 }
 26 if (action.type === 'search_blur') {// input失去光标事件触发的action
 27 const newState = JSON.parse(JSON.stringify(state))
 28 newState.focused = false;
 29 return newState
 30 }
 31 return state
 32 }
 33 // --------------------------分割线---------------------
 34 /**
 35 * 头部公共组件
 36 */
 37 import React, { Component } from 'react'
 38 import { connect } from 'react-redux'
 39 import {
 40 HeaderWrapper,
 41 Logo,
 42 Nav,
 43 NavItem,
 44 NavSearch,
 45 Addition,
 46 Button,
 47 SearchBox
 48 } from './style'
 49 class Header extends Component {
 50 render() {
 51 return (
 52 <HeaderWrapper>
 53 <Logo/>
 54 <Nav>
 55 <NavItem className='left active'>首页</NavItem>
 56 <NavItem className='left'>下载APP</NavItem>
 57 <SearchBox>
 58 <NavSearch
 59 onFocus = {this.props.handleInputFocus}
 60 onBlur = {this.props.handleInputBlur}
 61 className = {this.props.focused ? 'focused' : ''}
 62 />
 63 <i className = {this.props.focused ? 'focused iconfont' : 'iconfont'}>&#xe62b;</i>
 64 </SearchBox>
 65 <NavItem className='right icon'>
 66 <i className='iconfont'>&#xe636;</i>
 67 </NavItem>
 68 <NavItem className='right'>登陆</NavItem>
 69 </Nav>
 70 <Addition>
 71 <Button>注册</Button>
 72 <Button className='writting'>
 73 <i className='iconfont'>&#xe624;</i>
 74 写文章
 75 </Button>
 76 </Addition>
 77 </HeaderWrapper>
 78 )
 79 }
 80 }
 81 // 获取store中的数据
 82 const mapStateToprops = (state) => {
 83 return {
 84 focused: state.focused
 85 }
 86 }
 87 // 给reducer派发action改变store中的数据
 88 const mapDispathTpprops = (dispatch) => {
 89 return {
 90 handleInputFocus() {
 91 const action = {
 92 type: 'search_focus'
 93 }
 94 dispatch(action);
 95 },
 96 handleInputBlur() {
 97 const action = {
 98 type: 'search_blur'
 99 }
100 dispatch(action)
101 }
102 }
103 }
104 export default connect(mapStateToprops, mapDispathTpprops)(Header);
105 
106 
107 
108 /**
109 * App.js
110 */
111 import React from 'react';
112 import {GlobalStyle} from './style.js'
113 import {IconfontStyle} from './static/iconfont/iconfont.js'
114 import Header from './common/header'
115 import { Provider } from 'react-redux'; // 引入Provider,绑定store为最顶层props
116 import store from './store/index';
117 function App() {
118 return (
119 <div className="App">
120 <Provider store={store}>
121 <IconfontStyle/>
122 <GlobalStyle/>
123 <Header></Header>
124 </Provider>
125 </div>
126 );
127 }
128 
129 
130 export default App;
#### 使用combineReducers拆分合并reducer
- 之前我们将所有的数据都放置在store中,如果数据量庞大,这样并不适合管理
- 所以我们需要将之拆分成一个个模块,再将他们合并起来。
- 1. 在header中创建一个store文件夹
- 2. 在这个文件夹中创建reducer.js文件,和index.js文件
- 3. 将之前src目录中store中的reducer中的代码复制到header中的reducer中
- 4. 在header的store中的index.js将header中的reducer引入,并导出
- 5. 在src的store中的reducer中引入header中store的index.js
- 6. 在src中的reducer中引入combineReducers
- 7. reducer拆分了模块,所以使用store的state时,所以在header组件中获取数据要使用state.header.focused
- 8. 此时header中的index.js已经是一个无状态组件,所以我们对他进行改写成无状态组件有助于提升性能
- 具体请看下面代码
  1 /**
  2 * header中store代码
  3 */
  4 import reducer from './reducer'
  5 
  6 
  7 export { reducer }
  8 // ====================================
  9 const defaultState = {
 10 focused: false
 11 };
 12 export default (state=defaultState, action) => {
 13 if(action.type === 'search_focus'){
 14 const newState = JSON.parse(JSON.stringify(state))
 15 newState.focused = true;
 16 return newState
 17 }
 18 if (action.type === 'search_blur') {
 19 const newState = JSON.parse(JSON.stringify(state))
 20 newState.focused = false;
 21 return newState
 22 }
 23 return state
 24 }
 25 /**
 26 * src中的store
 27 */
 28 import { createStore, compose } from 'redux';
 29 import reducer from './reducer'
 30 
 31 
 32 const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
 33 const store = createStore(reducer, composeEnhancers());
 34 
 35 
 36 export default store;
 37 // ===========================================
 38 import { combineReducers } from 'redux'
 39 import { reducer as headerReducer } from '../common/header/store' // as创建别名
 40 const reducer = combineReducers ({
 41 header: headerReducer
 42 })
 43 
 44 
 45 export default reducer
 46 
 47 
 48 /**
 49 * header中的index.js文件
 50 */
 51 /**
 52 * 头部公共组件
 53 */
 54 import React from 'react'
 55 import { connect } from 'react-redux'
 56 import {
 57 HeaderWrapper,
 58 Logo,
 59 Nav,
 60 NavItem,
 61 NavSearch,
 62 Addition,
 63 Button,
 64 SearchBox
 65 } from './style'
 66 const Header = (props) => {
 67 return (
 68 <HeaderWrapper>
 69 <Logo/>
 70 <Nav>
 71 <NavItem className='left active'>首页</NavItem>
 72 <NavItem className='left'>下载APP</NavItem>
 73 <SearchBox>
 74 <NavSearch
 75 onFocus = {props.handleInputFocus}
 76 onBlur = {props.handleInputBlur}
 77 className = {props.focused ? 'focused' : ''}
 78 />
 79 <i className = {props.focused ? 'focused iconfont' : 'iconfont'}>&#xe62b;</i>
 80 </SearchBox>
 81 <NavItem className='right icon'>
 82 <i className='iconfont'>&#xe636;</i>
 83 </NavItem>
 84 <NavItem className='right'>登陆</NavItem>
 85 </Nav>
 86 <Addition>
 87 <Button>注册</Button>
 88 <Button className='writting'>
 89 <i className='iconfont'>&#xe624;</i>
 90 写文章
 91 </Button>
 92 </Addition>
 93 </HeaderWrapper>
 94 )
 95 }
 96 const mapStateToprops = (state) => {
 97 return {
 98 focused: state.header.focused
 99 }
100 }
101 const mapDispathTpprops = (dispatch) => {
102 return {
103 handleInputFocus() {
104 const action = {
105 type: 'search_focus'
106 }
107 dispatch(action);
108 },
109 handleInputBlur() {
110 const action = {
111 type: 'search_blur'
112 }
113 dispatch(action)
114 }
115 }
116 }
117 export default connect(mapStateToprops, mapDispathTpprops)(Header);
 
#### 通过 actionTypes 创建常量,actionCreators 创建action
- 在header文件中的store新建actionTypes.js和actionCreators.js文件
- 在actionTypes.js创建action中的type常量,并导出,再在index.js 和 actionCreators.js 还有reducer中引入
- 因为他们都需要使用action.type
- 在actionCreators.js中创建action并导出,再在index.js中引入。
- 具体使用和创建的代码如下
  1 /**
  2  * 创建action的函数
  3  */
  4 import * as constants from './actionTypes'
  5 export const searchFocus = () => ({
  6     type: constants.SEARCH_FOCUS
  7 })
  8 export const searchBlur = () => ({
  9     type: constants.SEARCH_BLUR
 10 })
 11 /**
 12  * 定义action常量
 13  */
 14 export const SEARCH_FOCUS = 'header/search_focus'
 15 export const SEARCH_BLUR = 'header/search_blur'
 16 
 17 import * as constants from './actionTypes'
 18 const defaultState = {
 19     focused: false
 20 };
 21 /**
 22  * reducer.js
 23 */
 24 export default (state=defaultState, action) => {
 25     if(action.type === constants.SEARCH_FOCUS){
 26         const newState = JSON.parse(JSON.stringify(state))
 27         newState.focused = true;
 28         return newState
 29     }
 30     if (action.type === constants.SEARCH_BLUR) {
 31         const newState = JSON.parse(JSON.stringify(state))
 32         newState.focused = false;
 33         return newState
 34     }
 35     return state
 36 }
 37 /**
 38  * header中store里面的index.js文件
 39 */
 40 import reducer from './reducer'
 41 import * as constants from './actionTypes'
 42 import * as actionCreators from './actionCreators'
 43 
 44 export { reducer, constants, actionCreators}
 45 /**
 46  * header的index.js组件,使用actionCreators创建action
 47 */
 48 /**
 49  * 头部公共组件
 50  */
 51 import React from 'react'
 52 import { connect } from 'react-redux'
 53 import { actionCreators } from './store' // 引入
 54 import {
 55     HeaderWrapper,
 56     Logo,
 57     Nav,
 58     NavItem,
 59     NavSearch,
 60     Addition,
 61     Button,
 62     SearchBox
 63 } from './style'
 64 const Header = (props) => {
 65     return (
 66         <HeaderWrapper>
 67             <Logo/>
 68             <Nav>
 69                 <NavItem className='left active'>首页</NavItem>
 70                 <NavItem className='left'>下载APP</NavItem>
 71                 <SearchBox>
 72                     <NavSearch 
 73                     onFocus = {props.handleInputFocus}
 74                     onBlur = {props.handleInputBlur}
 75                     className = {props.focused ? 'focused' : ''}
 76                     />
 77                     <i className = {props.focused ? 'focused iconfont' : 'iconfont'}>&#xe62b;</i>
 78                 </SearchBox>
 79                 <NavItem className='right icon'>
 80                     <i className='iconfont'>&#xe636;</i>
 81                 </NavItem>
 82                 <NavItem className='right'>登陆</NavItem>
 83             </Nav>
 84             <Addition>
 85                 <Button>注册</Button>
 86                 <Button className='writting'>
 87                     <i className='iconfont'>&#xe624;</i>
 88                     写文章
 89                 </Button>
 90             </Addition>
 91         </HeaderWrapper>
 92     )
 93 }
 94 const mapStateToprops = (state) => {
 95     return {
 96        focused: state.header.focused
 97     }
 98 }
 99 const mapDispathTpprops = (dispatch) => {
100     return {
101         handleInputFocus() {
102             dispatch(actionCreators.searchFocus());// 使用
103         },
104         handleInputBlur() {
105             dispatch(actionCreators.searchBlur()); // 使用
106         }
107     }
108 }
109 export default connect(mapStateToprops, mapDispathTpprops)(Header);

 

#### 引入immutable库,来进行reducer.js的操作
- reducer不能直接改变state,但是对于新手来说比较容易出错
- immutable.js库能生成一个immutable对象(不可改变的对象)
- 假设state是一个immutable对象,那么state就不可以被改变
- 安装immutable,yarn add immutable
- 在reducer中引入
- immutable对象通过get方法获取属性,通过set方法改变属性(并不是改变原来的值而是返回一个新的对象)
- 代码如下
 1 /**
 2 * reducer不能直接改变state,但是对于新手来说比较容易出错
 3 * immutable.js库能生成一个immutable对象(不可改变的对象)
 4 * 假设state是一个immutable对象,那么state就不可以被改变,
 5 */
 6 import * as constants from './actionTypes'
 7 import { fromJS } from 'immutable'
 8 const defaultState = fromJS({
 9 focused: false
10 });
11 
12 export default (state=defaultState, action) => {
13 if(action.type === constants.SEARCH_FOCUS){
14 // immutable对象的set方法,会结合之前的immutable对象的值,和设置的值返回一个全新的对象,所以他并没有改变原来的对象
15 return state.set('focused', true)
16 }
17 if (action.type === constants.SEARCH_BLUR) {
18 return state.set('focused', false)
19 }
20 return state
21 }
22 
23 /**
24 * 在组件中使用
25 */
26 /**
27 * 头部公共组件
28 */
29 import React from 'react'
30 import { connect } from 'react-redux'
31 import { actionCreators } from './store'
32 import {
33 HeaderWrapper,
34 Logo,
35 Nav,
36 NavItem,
37 NavSearch,
38 Addition,
39 Button,
40 SearchBox
41 } from './style'
42 const Header = (props) => {
43 return (
44 <HeaderWrapper>
45 <Logo/>
46 <Nav>
47 <NavItem className='left active'>首页</NavItem>
48 <NavItem className='left'>下载APP</NavItem>
49 <SearchBox>
50 <NavSearch
51 onFocus = {props.handleInputFocus}
52 onBlur = {props.handleInputBlur}
53 className = {props.focused ? 'focused' : ''}
54 />
55 <i className = {props.focused ? 'focused iconfont' : 'iconfont'}>&#xe62b;</i>
56 </SearchBox>
57 <NavItem className='right icon'>
58 <i className='iconfont'>&#xe636;</i>
59 </NavItem>
60 <NavItem className='right'>登陆</NavItem>
61 </Nav>
62 <Addition>
63 <Button>注册</Button>
64 <Button className='writting'>
65 <i className='iconfont'>&#xe624;</i>
66 写文章
67 </Button>
68 </Addition>
69 </HeaderWrapper>
70 )
71 }
72 const mapStateToprops = (state) => {
73 return {
74 focused: state.header.get('focused') // ========>使用get方法获取需要的属性
75 }
76 }
77 const mapDispathTpprops = (dispatch) => {
78 return {
79 handleInputFocus() {
80 dispatch(actionCreators.searchFocus());
81 },
82 handleInputBlur() {
83 dispatch(actionCreators.searchBlur())
84 }
85 }
86 }
87 export default connect(mapStateToprops, mapDispathTpprops)(Header);
 
#### 使用redux-immutable将state也改变成一个immutable对象
- 安装redux-immutable。 yarn add redux-immutable
- 修改src目录中的store,将reducer.js中引入的函数combineReducers,改成从redux-immutable中引入的。
- 由redux-immutable生成的combineReducers函数能返回一个immutable对象,这样state就变成了immutable对象
- 在header使用state的时候也要使用immutable对象的方法去获取数据,具体请看代码。
 1 /**
 2 * 头部公共组件
 3 */
 4 import React from 'react'
 5 import { connect } from 'react-redux'
 6 import { actionCreators } from './store'
 7 import {
 8 HeaderWrapper,
 9 Logo,
10 Nav,
11 NavItem,
12 NavSearch,
13 Addition,
14 Button,
15 SearchBox
16 } from './style'
17 const Header = (props) => {
18 return (
19 <HeaderWrapper>
20 <Logo/>
21 <Nav>
22 <NavItem className='left active'>首页</NavItem>
23 <NavItem className='left'>下载APP</NavItem>
24 <SearchBox>
25 <NavSearch
26 onFocus = {props.handleInputFocus}
27 onBlur = {props.handleInputBlur}
28 className = {props.focused ? 'focused' : ''}
29 />
30 <i className = {props.focused ? 'focused iconfont' : 'iconfont'}>&#xe62b;</i>
31 </SearchBox>
32 <NavItem className='right icon'>
33 <i className='iconfont'>&#xe636;</i>
34 </NavItem>
35 <NavItem className='right'>登陆</NavItem>
36 </Nav>
37 <Addition>
38 <Button>注册</Button>
39 <Button className='writting'>
40 <i className='iconfont'>&#xe624;</i>
41 写文章
42 </Button>
43 </Addition>
44 </HeaderWrapper>
45 )
46 }
47 const mapStateToprops = (state) => {
48 return {
49 // focused: state.get('header').get('focused'), // 两种写法一样
50 focused: state.getIn(['header', 'focused'] )
51 }
52 }
53 const mapDispathTpprops = (dispatch) => {
54 return {
55 handleInputFocus() {
56 dispatch(actionCreators.searchFocus());
57 },
58 handleInputBlur() {
59 dispatch(actionCreators.searchBlur())
60 }
61 }
62 }
63 export default connect(mapStateToprops, mapDispathTpprops)(Header);
64 
65 // =================================================================================
66 /**
67 * redux-immutable提供的combineReducers函数返回的是immutable对象
68 */
69 import { combineReducers } from 'redux-immutable'
70 import { reducer as headerReducer } from '../common/header/store'
71 export default combineReducers ({
72 header: headerReducer
73 })
 

#### 头部热门搜索的布局

- 这部分内容就是添加样式结构
- 直接上代码
  1 /**
  2 * header中的style.js
  3 */
  4 import styled from 'styled-components'
  5 import logo from '../../static/logo.png'
  6 
  7 export const HeaderWrapper = styled.div`
  8 position: relative;
  9 height: 58px;
 10 border-bottom: 1px solid #f0f0f0;
 11 &::after {
 12 content:'';
 13 display:block;
 14 clear:both;
 15 height:0;
 16 overflow:hidden;
 17 visibility:hidden;
 18 }
 19 `
 20 export const Logo = styled.a.attrs({
 21 href:'/'
 22 })`
 23 position: absolute
 24 display:block;
 25 height: 56px;
 26 width:100px;
 27 background: url(${logo});
 28 background-size: contain;
 29 `
 30 export const Nav = styled.div`
 31 width: 960px;
 32 padding-right: 70px;
 33 box-sizing: border-box;
 34 height: 100%
 35 margin: 0 auto;
 36 &::after {
 37 content:'';
 38 display:block;
 39 clear:both;
 40 height:0;
 41 overflow:hidden;
 42 visibility:hidden;
 43 }
 44 `
 45 export const NavItem = styled.div`
 46 line-height: 56px;
 47 padding: 0 15px;
 48 font-size: 17px;
 49 color: #333;
 50 cursor:pointer;
 51 &.icon {
 52 .iconfont {
 53 display:block;
 54 width: 24px;
 55 height: 24px;
 56 }
 57 }
 58 &.left {
 59 float: left;
 60 }
 61 &.right {
 62 float: right;
 63 color: #969696
 64 }
 65 &.active {
 66 color: #ea6f5a
 67 }
 68 `
 69 export const SearchBox = styled.div`
 70 position: relative;
 71 float: left;
 72 .iconfont {
 73 position: absolute;
 74 right: 15px;
 75 top: 13px;
 76 line-height: 30px;
 77 text-align: center;
 78 height: 30px;
 79 width: 30px;
 80 border-radius: 50%;
 81 cursor: pointer;
 82 transition: background 0.5s;
 83 -moz-transition: background 0.5s; /* Firefox 4 */
 84 -webkit-transition: background 0.5s; /* Safari 和 Chrome */
 85 -o-transition: background 0.5s; /* Opera */
 86 &.focused{
 87 background: #777;
 88 transition: background 0.5s;
 89 -moz-transition: background 0.5s; /* Firefox 4 */
 90 -webkit-transition: background 0.5s; /* Safari 和 Chrome */
 91 -o-transition: background 0.5s; /* Opera */
 92 }
 93 }
 94 `
 95 export const SearchItem = styled.a`
 96 dispaly: block;
 97 float: left;
 98 margin-right: 10px;
 99 margin-bottom: 10px;
100 height: 20xp;
101 padding: 2px 6px;
102 font-size: 12px;
103 color: #787878;
104 border: 1px solid #ddd;
105 border-radius: 3px;
106 &:hover {
107 color: #333;
108 border-color: #b4b4b4;
109 }
110 `
111 export const SearchSwitch = styled.div`
112 float: right;
113 font-size: 12px;
114 color: #999
115 `
116 export const SearchTitle = styled.div`
117 float: left;
118 font-size: 14px;
119 color: #999
120 
121 `
122 export const TitleBox = styled.div`
123 &.titleStyle {
124 padding-bottom: 10px;
125 }
126 &::after {
127 content:'';
128 display:block;
129 clear:both;
130 height:0;
131 overflow:hidden;
132 visibility:hidden;
133 }
134 `
135 export const SearchInFo = styled.div`
136 &::after {
137 content:'';
138 display:block;
139 clear:both;
140 height:0;
141 overflow:hidden;
142 visibility:hidden;
143 }
144 width: 240px;
145 position: absolute;
146 top: 58px;
147 left: 15px;
148 box-sizing: border-box;
149 padding: 20px;
150 padding-bottom: 10px;
151 border-radius: 3px;
152 border: 1px solid #f0f0f0;
153 background: #fff;
154 border-top: none;
155 box-shadow: 0 0 8px rgba(0,0,0,.2);
156 &::before {
157 content: "";
158 left: 15px;
159 width: 14px;
160 height: 14px;
161 background: #fff;
162 transform: rotate(45deg);
163 -ms-transform: rotate(45deg);
164 -webkit-transform: rotate(45deg);
165 top: -7px;
166 z-index: -1;
167 position: absolute;
168  
169 box-shadow: 0 0 8px rgba(0,0,0,.2);
170 }
171 `
172 export const NavSearch = styled.input.attrs({
173 placeholder: '搜索'
174 })`
175 width: 180px;
176 height: 38px;
177 margin: 9px 10px 9px 15px;
178 background: #eee;
179 border:none;
180 outline:none;
181 padding: 0 40px 0 20px;
182 border-radius: 19px;
183 font-size: 14px;
184 transition: width 0.5s;
185 -moz-transition: width 0.5s; /* Firefox 4 */
186 -webkit-transition: width 0.5s; /* Safari 和 Chrome */
187 -o-transition: width 0.5s; /* Opera */
188 &::placeholder {
189 color: #999
190 }
191 &.focused {
192 width: 240px;
193 transition: width 0.5s;
194 -moz-transition: width 0.5s; /* Firefox 4 */
195 -webkit-transition: width 0.5s; /* Safari 和 Chrome */
196 -o-transition: width 0.5s; /* Opera */
197 }
198 `
199 export const Addition = styled.div`
200 position: absolute;
201 right: 0;
202 top: 0;
203 height: 56px;
204 &::after {
205 content:'';
206 display:block;
207 clear:both;
208 height:0;
209 overflow:hidden;
210 visibility:hidden;
211 }
212 `
213 export const Button = styled.div`
214 cursor: pointer;
215 float: left;
216 width: 98px;
217 height: 38px;
218 box-sizing: border-box;
219 border: 1px solid #ea6f5a
220 padding: 5px 11px;
221 margin-top: 9px;
222 margin-right: 20px;
223 line-height: 26px;
224 text-align: center;
225 border-radius: 18px;
226 font-size: 14px;
227 color: #ea6f5a;
228 &.writting{
229 background: #ea6f5a;
230 color: #fff;
231 .iconfont {
232 display: inline-block;
233 width:19px;
234 height:19px;
235 }
236 }
237 `
238 /**
239 * 头部公共组件
240 */
241 import React from 'react'
242 import { connect } from 'react-redux'
243 import { actionCreators } from './store'
244 import {
245 HeaderWrapper,
246 Logo,
247 Nav,
248 NavItem,
249 NavSearch,
250 Addition,
251 Button,
252 SearchBox,
253 SearchTitle,
254 SearchSwitch,
255 SearchItem,
256 TitleBox,
257 SearchInFo
258 } from './style'
259 const getSearchInFo = (isShow) => {
260 if (isShow) {
261 return (
262 <SearchInFo>
263 <TitleBox className='titleStyle'>
264 <SearchTitle>热门搜索</SearchTitle>
265 <SearchSwitch>换一批</SearchSwitch>
266 </TitleBox>
267 <TitleBox>
268 <SearchItem>Numpy</SearchItem>
269 <SearchItem>电影</SearchItem>
270 <SearchItem>电影</SearchItem>
271 <SearchItem>电影</SearchItem>
272 <SearchItem>电影</SearchItem>
273 <SearchItem>电影</SearchItem>
274 <SearchItem>电影</SearchItem>
275 </TitleBox>
276 </SearchInFo>
277 )
278 }
279 return null
280 }
281 const Header = (props) => {
282 return (
283 <HeaderWrapper>
284 <Logo/>
285 <Nav>
286 <NavItem className='left active'>首页</NavItem>
287 <NavItem className='left'>下载APP</NavItem>
288 <SearchBox>
289 <NavSearch
290 onFocus = {props.handleInputFocus}
291 onBlur = {props.handleInputBlur}
292 className = {props.focused ? 'focused' : ''}
293 />
294 <i className = {props.focused ? 'focused iconfont' : 'iconfont'}>&#xe62b;</i>
295 {getSearchInFo(props.focused)}
296 </SearchBox>
297 <NavItem className='right icon'>
298 <i className='iconfont'>&#xe636;</i>
299 </NavItem>
300 <NavItem className='right'>登陆</NavItem>
301 </Nav>
302 <Addition>
303 <Button>注册</Button>
304 <Button className='writting'>
305 <i className='iconfont'>&#xe624;</i>
306 写文章
307 </Button>
308 </Addition>
309 </HeaderWrapper>
310 )
311 }
312 const mapStateToprops = (state) => {
313 return {
314 // focused: state.get('header').get('focused'), // 两种写法一样
315 focused: state.getIn(['header', 'focused'] )
316 }
317 }
318 const mapDispathTpprops = (dispatch) => {
319 return {
320 handleInputFocus() {
321 dispatch(actionCreators.searchFocus());
322 },
323 handleInputBlur() {
324 dispatch(actionCreators.searchBlur())
325 }
326 }
327 }
328 export default connect(mapStateToprops, mapDispathTpprops)(Header);

 

 
待续。。。。

 

转载于:https://www.cnblogs.com/boye-1990/p/11498070.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值