### 创建简书项目
- 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'></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'}></i> 38 </SearchBox> 39 <NavItem className='right icon'> 40 <i className='iconfont'></i> 41 </NavItem> 42 <NavItem className='right'>登陆</NavItem> 43 </Nav> 44 <Addition> 45 <Button>注册</Button> 46 <Button className='writting'> 47 <i className='iconfont'></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'}></i> 64 </SearchBox> 65 <NavItem className='right icon'> 66 <i className='iconfont'></i> 67 </NavItem> 68 <NavItem className='right'>登陆</NavItem> 69 </Nav> 70 <Addition> 71 <Button>注册</Button> 72 <Button className='writting'> 73 <i className='iconfont'></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'}></i> 80 </SearchBox> 81 <NavItem className='right icon'> 82 <i className='iconfont'></i> 83 </NavItem> 84 <NavItem className='right'>登陆</NavItem> 85 </Nav> 86 <Addition> 87 <Button>注册</Button> 88 <Button className='writting'> 89 <i className='iconfont'></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'}></i> 78 </SearchBox> 79 <NavItem className='right icon'> 80 <i className='iconfont'></i> 81 </NavItem> 82 <NavItem className='right'>登陆</NavItem> 83 </Nav> 84 <Addition> 85 <Button>注册</Button> 86 <Button className='writting'> 87 <i className='iconfont'></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'}></i> 56 </SearchBox> 57 <NavItem className='right icon'> 58 <i className='iconfont'></i> 59 </NavItem> 60 <NavItem className='right'>登陆</NavItem> 61 </Nav> 62 <Addition> 63 <Button>注册</Button> 64 <Button className='writting'> 65 <i className='iconfont'></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'}></i> 31 </SearchBox> 32 <NavItem className='right icon'> 33 <i className='iconfont'></i> 34 </NavItem> 35 <NavItem className='right'>登陆</NavItem> 36 </Nav> 37 <Addition> 38 <Button>注册</Button> 39 <Button className='writting'> 40 <i className='iconfont'></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'}></i> 295 {getSearchInFo(props.focused)} 296 </SearchBox> 297 <NavItem className='right icon'> 298 <i className='iconfont'></i> 299 </NavItem> 300 <NavItem className='right'>登陆</NavItem> 301 </Nav> 302 <Addition> 303 <Button>注册</Button> 304 <Button className='writting'> 305 <i className='iconfont'></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);
待续。。。。