Create by jsliang on 2019-4-7 19:37:41
Recently revised in 2019-04-23 09:40:44
Привет друзья, если вы считаете, что эта статья неплохая, не забудьте датьstar, друзья моиstarЭто моя мотивация продолжать обновлять!Адрес GitHub
【2019-08-16】Привет друзья, потому чтоjsliangБиблиотека документации подверглась рефакторингу, некоторые ссылки в этой статье могут быть неработоспособны, иjsliangИзвините за отсутствие сил поддерживать старые статьи на стороне Наггетс. Для тех, кому нужно получать последние статьи, щелкните адрес GitHub выше и перейдите в библиотеку документов, чтобы просмотреть скорректированные статьи.
Окончательный результат этой статьи:
Первоначально это была просто статья на главной странице и страница сведений о статье чистой имитации книги, но что-то произошло посередине (упомянутое в главе 19), так что конечным результатом стала смесь Jianshu и Nuggets~
каталог
Чем отличается передок без закидывания от соленой рыбы?
2 Предисловие
Время летит, время летит.
Как только вы решили что-то сделать, придерживайтесь этого.
Я верю, что настойчивость обязательно окупится, в каком бы аспекте она ни проявлялась.
Изучите React, зайдите в TodoList и идите дальше.
Три инициализируют каталог проекта
первый, импортируйте содержимое каталога Simplify в папку JianShu. или перейти к статье«React Demo One — TodoList»Ручное упрощение проекта.
Наш окончательный каталог выглядит так:
Друзья могут сами создавать новые пустые файлы, и в будущем они не вызовут путаницы, потому что не знают, где находится файл.
потом, проходим:
- Установить зависимости:
npm i
- Запустите проект:
npm run start
Запустите проект, и результаты будут следующими:
тогда, мы добавляем reset.css в каталог src, чтобы удалить дифференциальные эффекты различных браузеров.
src/reset.css
Сведения о коде
/*
* reset 的目的不是让默认样式在所有浏览器下一致,而是减少默认样式有可能带来的问题。
* The purpose of reset is not to allow default styles to be consistent across all browsers, but to reduce the potential problems of default styles.
* create by jsliang
*/
/** 清除内外边距 - clearance of inner and outer margins **/
body, h1, h2, h3, h4, h5, h6, hr, p, blockquote, /* 结构元素 - structural elements */
dl, dt, dd, ul, ol, li, /* 列表元素 - list elements */
pre, /* 文本格式元素 - text formatting elements */
form, fieldset, legend, button, input, textarea, /* 表单元素 - from elements */
th, td /* 表格元素 - table elements */ {
margin: 0;
padding: 0;
}
/** 设置默认字体 - setting the default font **/
body, button, input, select, textarea {
font: 18px/1.5 '黑体', Helvetica, sans-serif;
}
h1, h2, h3, h4, h5, h6, button, input, select, textarea { font-size: 100%; }
/** 重置列表元素 - reset the list element **/
ul, ol { list-style: none; }
/** 重置文本格式元素 - reset the text format element **/
a, a:hover { text-decoration: none; }
/** 重置表单元素 - reset the form element **/
button { cursor: pointer; }
input { font-size: 18px; outline: none; }
/** 重置表格元素 - reset the table element **/
table { border-collapse: collapse; border-spacing: 0; }
/*
* 图片自适应 - image responsize
* 1. 清空浏览器对图片的设置
* 2. <div>图片</div> 的情况下,图片会撑高 div,这么设置可以清除该影响
*/
img { border: 0; display: inline-block; width: 100%; max-width: 100%; height: auto; vertical-align: middle; }
/*
* 默认box-sizing是content-box,该属性导致padding会撑大div,使用border-box可以解决该问题
* set border-box for box-sizing when you use div, it solve the problem when you add padding and don't want to make the div width bigger
*/
div, input { box-sizing: border-box; }
/** 清除浮动 - clear float **/
.jsliang-clear:after, .clear:after {
content: '\20';
display: block;
height: 0;
clear: both;
}
.jsliang-clear, .clear {
*zoom: 1;
}
/** 设置input的placeholder - set input placeholder **/
input::-webkit-input-placeholder { color: #919191; font-size: 1em } /* Webkit browsers */
input::-moz-placeholder { color: #919191; font-size: 1em } /* Mozilla Firefox */
input::-ms-input-placeholder { color: #919191; font-size: 1em } /* Internet Explorer */
Кстати, создайте пустой файл глобальных стилей index.css.
И добавьте reset.css и index.css в index.js.
src/index.js
Сведения о коде
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './reset.css';
import './index.css';
ReactDOM.render(<App />, document.getElementById('root'));
Четыре создания компонентов React Head
первый, в каталоге src создайте новый общий каталог, а в общем каталоге создайте новый каталог заголовков, где содержимое index.js будет следующим:
src/common/header/index.js
Сведения о коде
import React, { Component } from 'react';
class Header extends Component {
render() {
return (
<div>
<h1>Header</h1>
</div>
)
}
}
export default Header;
потом, мы вводим header.js в App.js:
src/App.js
Сведения о коде
import React, { Component } from 'react';
import Header from './common/header';
class App extends Component {
render() {
return (
<div className="App">
<Header />
</div>
);
}
}
export default App;
Наконец, страница выглядит так:
Итак, мы завершили создание компонента Header.
5. Напишите короткую навигацию в шапке книги
первый, мы пишем index.js в src/common/header:
src/common/heder/index.js
Сведения о коде
import React, { Component } from 'react';
import './index.css';
import homeImage from '../../resources/img/header-home.png';
class Header extends Component {
constructor(props) {
super(props);
this.state = {
inputFocus: true
}
this.searchFocusOrBlur = this.searchFocusOrBlur.bind(this);
}
render() {
return (
<header>
<div className="header_left">
<a href="/">
<img alt="首页" src={homeImage} className="headef_left-img" />
</a>
</div>
<div className="header_center">
<div className="header_center-left">
<div className="nav-item header_center-left-home">
<i className="icon icon-home"></i>
<span>首页</span>
</div>
<div className="nav-item header_center-left-download">
<i className="icon icon-download"></i>
<span>下载App</span>
</div>
<div className="nav-item header_center-left-search">
<input
className={this.state.inputFocus ? 'input-nor-active' : 'input-active'}
placeholder="搜索"
onFocus={this.searchFocusOrBlur}
onBlur={this.searchFocusOrBlur}
/>
<i className={this.state.inputFocus ? 'icon icon-search' : 'icon icon-search icon-active'}></i>
</div>
</div>
<div className="header_center-right">
<div className="nav-item header_right-center-setting">
<span>Aa</span>
</div>
<div className="nav-item header_right-center-login">
<span>登录</span>
</div>
</div>
</div>
<div className="header_right nav-item">
<span className="header_right-register">注册</span>
<span className="header_right-write nav-item">
<i className="icon icon-write"></i>
<span>写文章</span>
</span>
</div>
</header>
)
}
searchFocusOrBlur(e) {
const inputFocus = this.state.inputFocus;
this.setState( () => ({
inputFocus: !inputFocus
}))
}
}
export default Header;
потом, мы добавляем стили CSS:
src/common/heder/index.css
Сведения о коде
header {
width: 100%;
height: 58px;
display: flex;
align-items: center;
border-bottom: 1px solid #ccc;
font-size: 17px;
}
.headef_left-img {
width: 100px;
height: 56px;
}
.header_center {
width: 1000px;
margin: 0 auto;
display: flex;
justify-content: space-between;
}
.nav-item {
margin-right: 30px;
display: flex;
align-items: center;
}
.header_center-left {
display: flex;
}
.header_center-left-home {
color: #ea6f5a;
}
.header_center-left-search {
position: relative;
}
.header_center-left-search input {
width: 240px;
padding: 0 40px 0 20px;
height: 38px;
font-size: 14px;
border: 1px solid #eee;
border-radius: 40px;
background: #eee;
}
.header_center-left-search .input-active {
width: 280px;
}
.header_center-left-search i {
position: absolute;
top: 8px;
right: 10px;
}
.header_center-left-search .icon-active {
padding: 3px;
top: 4px;
border-radius: 15px;
border: 1px solid #ea6f5a;
}
.header_center-left-search .icon-active:hover {
cursor: pointer;
}
.header_center-right {
display: flex;
color: #969696;
}
.header_right-register, .header_right-write {
width: 80px;
text-align: center;
height: 38px;
line-height: 38px;
border: 1px solid rgba(236,97,73,.7);
border-radius: 20px;
font-size: 15px;
color: #ea6f5a;
background-color: transparent;
}
.header_right-write {
margin-left: 10px;
padding-left: 10px;
margin-right: 0px;
color: #fff;
background-color: #ea6f5a;
}
тогда, из-за этих значков мы можем извлечь их в общую таблицу стилей, поэтому добавляем common.css в каталог src:
src/common.css
Сведения о коде
.icon {
display: inline-block;
width: 20px;
height: 21px;
margin-right: 5px;
}
.icon-home {
background: url('./resources/img/icon-home.png') no-repeat center;
background-size: 100%;
}
.icon-write {
background: url('./resources/img/icon-write.png') no-repeat center;
background-size: 100%;
}
.icon-download {
background: url('./resources/img/icon-download.png') no-repeat center;
background-size: 100%;
}
.icon-search {
background: url('./resources/img/icon-search.png') no-repeat center;
background-size: 100%;
}
Конечно, нам нужно место для хранения изображений, поэтому нам нужно создать новый каталог ресурсов в каталоге src и сохранить папку img в каталоге ресурсов, в которой хранятся эти файлы значков.
наконец, мы ссылаемся на common.css в index.js под src
src/index.js
Сведения о коде
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './reset.css';
import './index.css';
import './common.css';
ReactDOM.render(<App />, document.getElementById('root'));
Пока что наша страница отображается как:
Шесть установите анимацию поля ввода
Справочный адрес:react-transition-group
- Установите библиотеку анимации:
npm i react-transition-group -S
Измените код:
src/common/header/index.js
Сведения о коде
import React, { Component } from 'react';
// 1. 引入动画库
import { CSSTransition } from 'react-transition-group';
import './index.css';
import homeImage from '../../resources/img/header-home.png';
class Header extends Component {
constructor(props) {
super(props);
this.state = {
inputBlur: true
}
this.searchFocusOrBlur = this.searchFocusOrBlur.bind(this);
}
render() {
return (
<header>
<div className="header_left">
<a href="/">
<img alt="首页" src={homeImage} className="headef_left-img" />
</a>
</div>
<div className="header_center">
<div className="header_center-left">
<div className="nav-item header_center-left-home">
<i className="icon icon-home"></i>
<span>首页</span>
</div>
<div className="nav-item header_center-left-download">
<i className="icon icon-download"></i>
<span>下载App</span>
</div>
<div className="nav-item header_center-left-search">
{/* 2. 通过 CSSTransition 包裹 input */}
<CSSTransition
in={this.state.inputBlur}
timeout={200}
classNames="slide"
>
<input
className={this.state.inputBlur ? 'input-nor-active' : 'input-active'}
placeholder="搜索"
onFocus={this.searchFocusOrBlur}
onBlur={this.searchFocusOrBlur}
/>
</CSSTransition>
<i className={this.state.inputBlur ? 'icon icon-search' : 'icon icon-search icon-active'}></i>
</div>
</div>
<div className="header_center-right">
<div className="nav-item header_right-center-setting">
<span>Aa</span>
</div>
<div className="nav-item header_right-center-login">
<span>登录</span>
</div>
</div>
</div>
<div className="header_right nav-item">
<span className="header_right-register">注册</span>
<span className="header_right-write nav-item">
<i className="icon icon-write"></i>
<span>写文章</span>
</span>
</div>
</header>
)
}
searchFocusOrBlur(e) {
const inputBlur = this.state.inputBlur;
this.setState( () => ({
inputBlur: !inputBlur
}))
}
}
export default Header;
src/common/header/index.css
Сведения о коде
header {
width: 100%;
height: 58px;
display: flex;
align-items: center;
border-bottom: 1px solid #ccc;
font-size: 17px;
}
.headef_left-img {
width: 100px;
height: 56px;
}
.header_center {
width: 1000px;
margin: 0 auto;
display: flex;
justify-content: space-between;
}
.nav-item {
margin-right: 30px;
display: flex;
align-items: center;
}
.header_center-left {
display: flex;
}
.header_center-left-home {
color: #ea6f5a;
}
.header_center-left-search {
position: relative;
}
/* 3. 编写对应的 CSS 样式 */
.slide-enter {
transition: all .2s ease-out;
}
.slide-enter-active {
width: 280px;
}
.slide-exit {
transition: all .2s ease-out;
}
.silde-exit-active {
width: 240px;
}
/* 3. 结束 */
.header_center-left-search input {
width: 240px;
padding: 0 40px 0 20px;
height: 38px;
font-size: 14px;
border: 1px solid #eee;
border-radius: 40px;
background: #eee;
}
.header_center-left-search .input-active {
width: 280px;
}
.header_center-left-search i {
position: absolute;
top: 8px;
right: 10px;
}
.header_center-left-search .icon-active {
padding: 3px;
top: 4px;
border-radius: 15px;
border: 1px solid #ea6f5a;
}
.header_center-left-search .icon-active:hover {
cursor: pointer;
}
.header_center-right {
display: flex;
color: #969696;
}
.header_right-register, .header_right-write {
width: 80px;
text-align: center;
height: 38px;
line-height: 38px;
border: 1px solid rgba(236,97,73,.7);
border-radius: 20px;
font-size: 15px;
color: #ea6f5a;
background-color: transparent;
}
.header_right-write {
margin-left: 10px;
padding-left: 10px;
margin-right: 0px;
color: #fff;
background-color: #ea6f5a;
}
Таким образом, после четырех шагов:
- Установите библиотеку анимации:
npm i react-transition-group -S
- Познакомить с анимационной библиотекой
- пройти через
CSSTransition
пакетinput
- Напишите соответствующие стили CSS
Мы успешно реализовали внедрение и использование плагинов анимации CSS.На данный момент страница отображается как:
Семь оптимизированных кодов
- Установите Редукс:
npm i redux -S
- Установите React-Redux:
npm i react-redux -S
- Начните добавлять Redux и React-Redux в свой код
- первый, создайте папку хранилища и внутри нее создайте index.js и reducer.js:
src/store/index.js
Сведения о коде
import { createStore } from 'redux';
import reducer from './reducer';
const store = createStore(reducer);
export default store;
src/store/reducer.js
Сведения о коде
const defaultState = {
inputBlur: true
};
export default (state = defaultState, action) => {
return state;
}
- тогда, ссылайтесь на react-redux и store/index.js в App.js:
src/App.js
Сведения о коде
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import Header from './common/header';
import store from './store';
class App extends Component {
render() {
return (
<Provider store={store} className="App">
<Header />
</Provider>
);
}
}
export default App;
- потом, измените содержимое index.js в заголовке common под src:
src/common/header/index.js
Сведения о коде
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { CSSTransition } from 'react-transition-group';
import './index.css';
import homeImage from '../../resources/img/header-home.png';
class Header extends Component {
render() {
return (
<header>
<div className="header_left">
<a href="/">
<img alt="首页" src={homeImage} className="headef_left-img" />
</a>
</div>
<div className="header_center">
<div className="header_center-left">
<div className="nav-item header_center-left-home">
<i className="icon icon-home"></i>
<span>首页</span>
</div>
<div className="nav-item header_center-left-download">
<i className="icon icon-download"></i>
<span>下载App</span>
</div>
<div className="nav-item header_center-left-search">
<CSSTransition
in={this.props.inputBlur}
timeout={200}
classNames="slide"
>
<input
className={this.props.inputBlur ? 'input-nor-active' : 'input-active'}
placeholder="搜索"
onFocus={this.props.searchFocusOrBlur}
onBlur={this.props.searchFocusOrBlur}
/>
</CSSTransition>
<i className={this.props.inputBlur ? 'icon icon-search' : 'icon icon-search icon-active'}></i>
</div>
</div>
<div className="header_center-right">
<div className="nav-item header_right-center-setting">
<span>Aa</span>
</div>
<div className="nav-item header_right-center-login">
<span>登录</span>
</div>
</div>
</div>
<div className="header_right nav-item">
<span className="header_right-register">注册</span>
<span className="header_right-write nav-item">
<i className="icon icon-write"></i>
<span>写文章</span>
</span>
</div>
</header>
)
}
}
const mapStateToProps = (state) => {
return {
inputBlur: state.inputBlur
}
}
const mapDispathToProps = (dispatch) => {
return {
searchFocusOrBlur() {
const action = {
type: 'search_focus_or_blur'
}
dispatch(action);
}
}
}
export default connect(mapStateToProps, mapDispathToProps)(Header);
-
приходи еще, давайте модифицируем reducer.js, чтобы он получал и обрабатывал в src/index.js
dispatch
Пришедшее значение:
src/store/reducer.js
Сведения о коде
const defaultState = {
inputBlur: true
};
export default (state = defaultState, action) => {
if(action.type === 'search_focus_or_blur') {
const newState = JSON.parse(JSON.stringify(state));
newState.inputBlur = !newState.inputBlur
return newState;
}
return state;
}
-
В настоящее время, мы завершили шаги модификации. В то же время, в настоящее время, поскольку index.js в шапке в общем под src содержит только
render
Тело метода, которое представляет собой компонент без состояния, поэтому мы преобразуем его в компонент без состояния:
src/common/header/index.js
Сведения о коде
import React from 'react';
import { connect } from 'react-redux';
import { CSSTransition } from 'react-transition-group';
import './index.css';
import homeImage from '../../resources/img/header-home.png';
const Header = (props) => {
return (
<header>
<div className="header_left">
<a href="/">
<img alt="首页" src={homeImage} className="headef_left-img" />
</a>
</div>
<div className="header_center">
<div className="header_center-left">
<div className="nav-item header_center-left-home">
<i className="icon icon-home"></i>
<span>首页</span>
</div>
<div className="nav-item header_center-left-download">
<i className="icon icon-download"></i>
<span>下载App</span>
</div>
<div className="nav-item header_center-left-search">
<CSSTransition
in={props.inputBlur}
timeout={200}
classNames="slide"
>
<input
className={props.inputBlur ? 'input-nor-active' : 'input-active'}
placeholder="搜索"
onFocus={props.searchFocusOrBlur}
onBlur={props.searchFocusOrBlur}
/>
</CSSTransition>
<i className={props.inputBlur ? 'icon icon-search' : 'icon icon-search icon-active'}></i>
</div>
</div>
<div className="header_center-right">
<div className="nav-item header_right-center-setting">
<span>Aa</span>
</div>
<div className="nav-item header_right-center-login">
<span>登录</span>
</div>
</div>
</div>
<div className="header_right nav-item">
<span className="header_right-register">注册</span>
<span className="header_right-write nav-item">
<i className="icon icon-write"></i>
<span>写文章</span>
</span>
</div>
</header>
)
}
const mapStateToProps = (state) => {
return {
inputBlur: state.inputBlur
}
}
const mapDispathToProps = (dispatch) => {
return {
searchFocusOrBlur() {
const action = {
type: 'search_focus_or_blur'
}
dispatch(action);
}
}
}
export default connect(mapStateToProps, mapDispathToProps)(Header);
- наконец, мы завершили справку и использование Redux, React-Redux и обновление компонента без сохранения состояния header/index.js.
Поскольку мы храним в состоянии только необходимые данные, стиль и функции не меняются, поэтому рендеры публиковаться не будут.
Восемь с использованием плагина redux-devtools-extension
Измените src/store/index.js следующим образом:
src/store/index.js
Сведения о коде
import { 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-extension, который был установлен ранее.
Используй это:
Девять оптимизаций: извлеките reducer.js
В ходе разработки проекта мы обнаружим, что по мере развития проекта reducer.js становится все больше и больше и, наконец, становится неподдерживаемым.
Лектор МООК для видео также упомянул:Когда ваш js-файл содержит более 300 строк кода, это означает, что его дизайн изначально неразумен.
Значит, надо думать о дальнейшей оптимизации.
первый, мы создаем новое хранилище в каталоге заголовков, создаем новый файл reducer.js и вырезаем содержимое из файла reducer.js из src/store в header/store/reducer.js:
src/common/header/store/reducer.js
Сведения о коде
// 1. 将 reducer.js 转移到 header/store/reducer.js 中
const defaultState = {
inputBlur: true
};
export default (state = defaultState, action) => {
if(action.type === 'search_focus_or_blur') {
const newState = JSON.parse(JSON.stringify(state));
newState.inputBlur = !newState.inputBlur
return newState;
}
return state;
}
потом, мы изменяем содержимое src/store/reducer.js на:
src/store/reducer.js
Сведения о коде
// 2. 通过 combineReducers 整合多个 reducer.js 文件
import { combineReducers } from 'redux';
import headerReducer from '../common/header/store/reducer';
const reducer = combineReducers({
header: headerReducer
})
export default reducer;
наконец, мы модифицируем содержимое src/common/header/index.js:
src/common/header/index.js
Сведения о коде
// 代码省略 。。。
const mapStateToProps = (state) => {
return {
// 3. 因为引用的层级变了,所以需要修改 state.inputBlur 为 state.header.inputBlue
inputBlur: state.header.inputBlur
}
}
// 代码省略 。。。
Здесь нам нужно знать следующее: раньше у нас был только один слой каталогов, поэтому модификацияstate.inputBlur
.
Однако, поскольку черезcombineReducers
Интегрируйте редуктор.js, поэтому его необходимо изменить, чтобыstate.header.inputBlur
На данный момент мы завершили оптимизацию reducer.js.
Десять оптимизаций: извлечение действий
- первый, создайте новый файл actionCreators.js в хранилище заголовков:
src/common/header/store/actionCreators.js
Сведения о коде
// 1. 定义 actionCreators
export const searchFocusOrBlur = () => ({
type: 'search_focus_or_blur'
})
-
потом, в файле index.js в шапке вносим actionCreators.js, а в
mapDispathToProps
тело методаdispatch
выйди:
src/common/header/index.js
Сведения о коде
import React from 'react';
import { connect } from 'react-redux';
import { CSSTransition } from 'react-transition-group';
import './index.css';
// 2. 以 actionCreators 的形式将所有 action 引入进来
import * as actionCreators from './store/actionCreators';
import homeImage from '../../resources/img/header-home.png';
const Header = (props) => {
return (
<header>
<div className="header_left">
<a href="/">
<img alt="首页" src={homeImage} className="headef_left-img" />
</a>
</div>
<div className="header_center">
<div className="header_center-left">
<div className="nav-item header_center-left-home">
<i className="icon icon-home"></i>
<span>首页</span>
</div>
<div className="nav-item header_center-left-download">
<i className="icon icon-download"></i>
<span>下载App</span>
</div>
<div className="nav-item header_center-left-search">
<CSSTransition
in={props.inputBlur}
timeout={200}
classNames="slide"
>
<input
className={props.inputBlur ? 'input-nor-active' : 'input-active'}
placeholder="搜索"
onFocus={props.searchFocusOrBlur}
onBlur={props.searchFocusOrBlur}
/>
</CSSTransition>
<i className={props.inputBlur ? 'icon icon-search' : 'icon icon-search icon-active'}></i>
</div>
</div>
<div className="header_center-right">
<div className="nav-item header_right-center-setting">
<span>Aa</span>
</div>
<div className="nav-item header_right-center-login">
<span>登录</span>
</div>
</div>
</div>
<div className="header_right nav-item">
<span className="header_right-register">注册</span>
<span className="header_right-write nav-item">
<i className="icon icon-write"></i>
<span>写文章</span>
</span>
</div>
</header>
)
}
const mapStateToProps = (state) => {
return {
inputBlur: state.header.inputBlur
}
}
const mapDispathToProps = (dispatch) => {
return {
searchFocusOrBlur() {
// 3. 使用 actionCreators
dispatch(actionCreators.searchFocusOrBlur());
}
}
}
export default connect(mapStateToProps, mapDispathToProps)(Header);
-
тогда, потому что мы использовали в actionCreators.js
type
является строкой, поэтому мы также создаем actionTypes.js в хранилище и делаем его константой:
src/common/header/store/actionTypes.js
Сведения о коде
export const SEARCH_FOCUS_OR_BLUR = 'search_focus_or_blur';
- А потом, мы вводим actionTypes.js в actionCreators.js:
src/common/header/store/actionCreators.js
Сведения о коде
// 4. 引入常量
import { SEARCH_FOCUS_OR_BLUR } from './actionTypes';
// 1. 定义 actionCreators
// 5. 将 action 中的字符串修改为常量
export const searchFocusOrBlur = () => ({
type: SEARCH_FOCUS_OR_BLUR
})
- тогда продолжай, мы модифицируем reducer.js под хранилищем в директории header, потому что наша строка стала константой, поэтому нам также нужно внести соответствующие изменения здесь:
src/common/header/store/reducer.js
Сведения о коде
// 6. 引入常量
import * as actionTypes from './actionTypes'
const defaultState = {
inputBlur: true
};
export default (state = defaultState, action) => {
// 7. 使用常量
if(action.type === actionTypes.SEARCH_FOCUS_OR_BLUR) {
const newState = JSON.parse(JSON.stringify(state));
newState.inputBlur = !newState.inputBlur
return newState;
}
return state;
}
- потом, теперь у нас есть три файла в каталоге header/store: actionCreators.js, actionTypes.js и reducer.js.Если нам приходится находить их один за другим каждый раз, когда мы импортируем, это довольно хлопотно, поэтому мы находимся в header/store. Создайте новый index.js и управляйте этими тремя файлами через index.js, чтобы, когда нам нужно импортировать их на другие страницы, нам нужно было только импортировать index.js в хранилище.
src/common/header/store/index.js
Сведения о коде
// 8. 统一管理 store 目录中的文件
import * as actionCreators from './actionCreators';
import * as actionTypes from './actionTypes';
import reducer from './reducer';
export { actionCreators, actionTypes, reducer };
- В настоящее время, стоит отметить, что в это время нам нужно обработать файл header/index.js:
Сведения о коде
import React from 'react';
import { connect } from 'react-redux';
import { CSSTransition } from 'react-transition-group';
import './index.css';
// 2. 以 actionCreators 的形式将所有 action 引入进来
// import * as actionCreators from './store/actionCreators';
// 9. 引入 store/index 文件即可
import { actionCreators } from './store';
import homeImage from '../../resources/img/header-home.png';
// 代码省略
- наконец, а затем обработайте src/store/reducer.js, так как он ссылается на reducer.js в common/header/store:
Сведения о коде
import { combineReducers } from 'redux';
// 10. 修改下引用方式
import { reducer as headerReducer } from '../common/header/store';
const reducer = combineReducers({
header: headerReducer
})
export default reducer;
На данный момент мы завершили это оптимизационное извлечение.
11 Оптимизация; immutable.js
В процессе нашей работы, если мы не будем осторожны, данные в reducer.js будут модифицированы (в обычной разработке мы будем передаватьJSON.parse(JSON.stringify())
сделать глубокую копию и получить дополнительную копию для изменения).
Итак, на данный момент нам нужно использовать immutable.js, который был разработан командой Facebook, чтобы помочь нам создаватьimmutable
объекта, тем самым ограничиваяstate
не может быть изменен.
- Установите неизменяемый.js:
npm i immutable -S
. - Дело immutable.js:
const { Map } = require('immutable');
const map1 = Map({ a: 1, b: 2, c: 3 });
const map2 = map1.set('b', 50);
map1.get('b') + " vs. " + map2.get('b'); // 2 vs. 50
Выглядит очень просто, мы используем его прямо в Jianshu Demo:
src/common/header/store/reducer.js
Сведения о коде
import * as actionTypes from './actionTypes'
// 1. 通过 immutable 引入 fromJS
import { fromJS } from 'immutable';
// 2. 对 defaultState 使用 fromJS
const defaultState = fromJS({
inputBlur: true
});
export default (state = defaultState, action) => {
if(action.type === actionTypes.SEARCH_FOCUS_OR_BLUR) {
// const newState = JSON.parse(JSON.stringify(state));
// newState.inputBlur = !newState.inputBlur
// return newState;
// 4. 通过 immutable 的方法来 set state 的值
// immutable 对象的 set 方法,会结合之前 immutable 对象的值和设置的值,返回一个全新的对象
return state.set('inputBlur', !state.get('inputBlur'));
}
return state;
}
src/common/header/index.js
Сведения о коде
import React from 'react';
import { connect } from 'react-redux';
import { CSSTransition } from 'react-transition-group';
import './index.css';
import { actionCreators } from './store';
import homeImage from '../../resources/img/header-home.png';
const Header = (props) => {
return (
<header>
<div className="header_left">
<a href="/">
<img alt="首页" src={homeImage} className="headef_left-img" />
</a>
</div>
<div className="header_center">
<div className="header_center-left">
<div className="nav-item header_center-left-home">
<i className="icon icon-home"></i>
<span>首页</span>
</div>
<div className="nav-item header_center-left-download">
<i className="icon icon-download"></i>
<span>下载App</span>
</div>
<div className="nav-item header_center-left-search">
<CSSTransition
in={props.inputBlur}
timeout={200}
classNames="slide"
>
<input
className={props.inputBlur ? 'input-nor-active' : 'input-active'}
placeholder="搜索"
onFocus={props.searchFocusOrBlur}
onBlur={props.searchFocusOrBlur}
/>
</CSSTransition>
<i className={props.inputBlur ? 'icon icon-search' : 'icon icon-search icon-active'}></i>
</div>
</div>
<div className="header_center-right">
<div className="nav-item header_right-center-setting">
<span>Aa</span>
</div>
<div className="nav-item header_right-center-login">
<span>登录</span>
</div>
</div>
</div>
<div className="header_right nav-item">
<span className="header_right-register">注册</span>
<span className="header_right-write nav-item">
<i className="icon icon-write"></i>
<span>写文章</span>
</span>
</div>
</header>
)
}
const mapStateToProps = (state) => {
return {
// 3. 通过 immutable 提供的 get() 方法来获取 inputBlur 属性
inputBlur: state.header.get('inputBlur')
}
}
const mapDispathToProps = (dispatch) => {
return {
searchFocusOrBlur() {
dispatch(actionCreators.searchFocusOrBlur());
}
}
}
export default connect(mapStateToProps, mapDispathToProps)(Header);
Мы примерно сделали четыре шага, чтобы завершить справку и использовать immutable.js:
- пройти через
import
immutable
представлятьfromJS
- правильно
defaultState
использоватьfromJS
- В настоящее время мы не можем напрямую изменять
matStateToProps
значение в , но поimmutable
который предоставилget()
способ получитьinputBlur
Атрибуты - пройти через
immutable
используя этот метод дляset
state
ценность .immutable
объектset
Методы будут объединены доimmutable
Значение объекта и значение множества, возвращающее совершенно новый объект
Таким образом, мы успешно защитилиstate
ценность .
Двенадцать оптимизаций: редукционно-неизменяемый
Конечно, выше мы защищаем заголовок вstate
, мы в коде:
inputBlur: state.header.get('inputBlur')
этоheader
Слишкомstate
Значение , поэтому нам также нужно его защитить, поэтому нам нужен redux-immutable
- Установите редукционно-неизменяемый:
npm i redux-immutable -S
- Используйте редукционно-неизменяемый:
src/store/reducer.js
Сведения о коде
// import { combineReducers } from 'redux';
// 1. 通过 redux-immutable 引入 combineReducers 而非原先的 redux
import { combineReducers } from 'redux-immutable';
import { reducer as headerReducer } from '../common/header/store';
const reducer = combineReducers({
header: headerReducer
})
export default reducer;
src/common/header/index.js
Сведения о коде
// 代码省略。。。
const mapStateToProps = (state) => {
return {
// 2. 通过同样的 get 方法来获取 header
inputBlur: state.get('header').get('inputBlur')
}
}
// 代码省略。。。
Таким образом, в три простых шага мы защитили Господаstate
Значение:
- Установите редукционно-неизменяемый:
npm i redux-immutable -S
- Представлено redux-immutable
combineReducers
вместо оригинального редукса - через тот же
get
способ получитьheader
Реализация тринадцати функций: популярные запросы
Эта глава выполняет три функции:
- Написать Популярный поиск Показать Скрыть
- Установить редукционный преобразователь
- Используйте функцию фальсификации данных, предоставляемую Node в React, напишите файл headerList.json под public/api и создайте поддельные данные.Метод использования следующий
axios.get('/api/headerList.json').then()
первый, завершаем отображение и скрытие популярных запросов:
src/common.css
Сведения о коде
.icon {
display: inline-block;
width: 20px;
height: 21px;
margin-right: 5px;
}
.icon-home {
background: url('./resources/img/icon-home.png') no-repeat center;
background-size: 100%;
}
.icon-write {
background: url('./resources/img/icon-write.png') no-repeat center;
background-size: 100%;
}
.icon-download {
background: url('./resources/img/icon-download.png') no-repeat center;
background-size: 100%;
}
.icon-search {
background: url('./resources/img/icon-search.png') no-repeat center;
background-size: 100%;
}
.display-hide {
display: none;
}
.display-show {
display: block;
}
src/common/header/index.css
Сведения о коде
header {
width: 100%;
height: 58px;
display: flex;
align-items: center;
border-bottom: 1px solid #ccc;
font-size: 17px;
}
/* 头部左边 */
.header_left-img {
width: 100px;
height: 56px;
}
/* 头部中间 */
.header_center {
width: 1000px;
margin: 0 auto;
display: flex;
justify-content: space-between;
}
.nav-item {
margin-right: 30px;
display: flex;
align-items: center;
}
/* 头部中间左部 */
.header_center-left {
display: flex;
}
/* 头部中间左部 - 首页 */
.header_center-left-home {
color: #ea6f5a;
}
/* 头部中间左部 - 搜索框 */
.header_center-left-search {
position: relative;
}
.slide-enter {
transition: all .2s ease-out;
}
.slide-enter-active {
width: 280px;
}
.slide-exit {
transition: all .2s ease-out;
}
.silde-exit-active {
width: 240px;
}
.header_center-left-search input {
width: 240px;
padding: 0 45px 0 20px;
height: 38px;
font-size: 14px;
border: 1px solid #eee;
border-radius: 40px;
background: #eee;
}
.header_center-left-search .input-active {
width: 280px;
}
.header_center-left-search .icon-search {
position: absolute;
top: 8px;
right: 10px;
}
.header_center-left-search .icon-active {
padding: 3px;
top: 4px;
border-radius: 15px;
border: 1px solid #ea6f5a;
}
/* 头部中间左部 - 热搜 */
.header_center-left-search .icon-active:hover {
cursor: pointer;
}
.header_center-left-hot-search:before {
content: "";
left: 27px;
width: 10px;
height: 10px;
transform: rotate(45deg);
top: -5px;
z-index: -1;
position: absolute;
background-color: #fff;
box-shadow: 0 0 8px rgba(0,0,0,.2);
}
.header_center-left-hot-search {
position: absolute;
width: 250px;
left: 0;
top: 125%;
padding: 15px;
font-size: 14px;
background: #fff;
border-radius: 4px;
box-shadow: 0 0 8px rgba(0, 0, 0, 0.2);
}
.header_center-left-hot-search-title {
display: flex;
justify-content: space-between;
color: #969696;
}
.header_center-left-hot-search-change {
display: flex;
justify-content: space-between;
align-items: center;
}
.icon-change {
display: inline-block;
width: 20px;
height: 14px;
background: url('../../resources/img/icon-change.png') no-repeat center;
background-size: 100%;
}
.icon-change:hover {
cursor: pointer;
}
.header_center-left-hot-search-content span {
display: inline-block;
margin-top: 10px;
margin-right: 10px;
padding: 2px 6px;
font-size: 12px;
color: #787878;
border: 1px solid #ddd;
border-radius: 3px;
}
.header_center-left-hot-search-content span:hover {
cursor: pointer;
}
/* 头部中间右部 */
.header_center-right {
display: flex;
color: #969696;
}
/* 头部右边 */
.header_right-register, .header_right-write {
width: 80px;
text-align: center;
height: 38px;
line-height: 38px;
border: 1px solid rgba(236,97,73,.7);
border-radius: 20px;
font-size: 15px;
color: #ea6f5a;
background-color: transparent;
}
.header_right-write {
margin-left: 10px;
padding-left: 10px;
margin-right: 0px;
color: #fff;
background-color: #ea6f5a;
}
src/common/header/index.js
Сведения о коде
import React from 'react';
import { connect } from 'react-redux';
import { CSSTransition } from 'react-transition-group';
import './index.css';
import { actionCreators } from './store';
import homeImage from '../../resources/img/header-home.png';
const Header = (props) => {
return (
<header>
<div className="header_left">
<a href="/">
<img alt="首页" src={homeImage} className="header_left-img" />
</a>
</div>
<div className="header_center">
<div className="header_center-left">
<div className="nav-item header_center-left-home">
<i className="icon icon-home"></i>
<span>首页</span>
</div>
<div className="nav-item header_center-left-download">
<i className="icon icon-download"></i>
<span>下载App</span>
</div>
<div className="nav-item header_center-left-search">
<CSSTransition
in={props.inputBlur}
timeout={200}
classNames="slide"
>
<input
className={props.inputBlur ? 'input-nor-active' : 'input-active'}
placeholder="搜索"
onFocus={props.searchFocusOrBlur}
onBlur={props.searchFocusOrBlur}
/>
</CSSTransition>
<i className={props.inputBlur ? 'icon icon-search' : 'icon icon-search icon-active'}></i>
{/* 添加热搜模块 */}
<div className={props.inputBlur ? 'display-hide header_center-left-hot-search' : 'display-show header_center-left-hot-search'}>
<div className="header_center-left-hot-search-title">
<span>热门搜索</span>
<span>
<i className="icon-change"></i>
<span>换一批</span>
</span>
</div>
<div className="header_center-left-hot-search-content">
<span>考研</span>
<span>慢死人</span>
<span>悦心</span>
<span>一致</span>
<span>是的</span>
<span>jsliang</span>
</div>
</div>
</div>
</div>
<div className="header_center-right">
<div className="nav-item header_right-center-setting">
<span>Aa</span>
</div>
<div className="nav-item header_right-center-login">
<span>登录</span>
</div>
</div>
</div>
<div className="header_right nav-item">
<span className="header_right-register">注册</span>
<span className="header_right-write nav-item">
<i className="icon icon-write"></i>
<span>写文章</span>
</span>
</div>
</header>
)
}
const mapStateToProps = (state) => {
return {
inputBlur: state.get('header').get('inputBlur')
}
}
const mapDispathToProps = (dispatch) => {
return {
searchFocusOrBlur() {
dispatch(actionCreators.searchFocusOrBlur());
}
}
}
export default connect(mapStateToProps, mapDispathToProps)(Header);
Из этого мы доделали отображение и скрытие популярных поисков:
PS: Поскольку страница постепенно увеличивается, использование компонентов без состояния в нашем шапке больше не может соответствовать нашим требованиям.Нам нужно изменить компоненты без состояния на обычные компоненты:
src/common/header/index.js
Сведения о коде
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { CSSTransition } from 'react-transition-group';
import './index.css';
import { actionCreators } from './store';
import homeImage from '../../resources/img/header-home.png';
class Header extends Component {
render() {
return (
<header>
<div className="header_left">
<a href="/">
<img alt="首页" src={homeImage} className="header_left-img" />
</a>
</div>
<div className="header_center">
<div className="header_center-left">
<div className="nav-item header_center-left-home">
<i className="icon icon-home"></i>
<span>首页</span>
</div>
<div className="nav-item header_center-left-download">
<i className="icon icon-download"></i>
<span>下载App</span>
</div>
<div className="nav-item header_center-left-search">
<CSSTransition
in={this.props.inputBlur}
timeout={200}
classNames="slide"
>
<input
className={this.props.inputBlur ? 'input-nor-active' : 'input-active'}
placeholder="搜索"
onFocus={this.props.searchFocusOrBlur}
onBlur={this.props.searchFocusOrBlur}
/>
</CSSTransition>
<i className={this.props.inputBlur ? 'icon icon-search' : 'icon icon-search icon-active'}></i>
<div className={this.props.inputBlur ? 'display-hide header_center-left-hot-search' : 'display-show header_center-left-hot-search'}>
<div className="header_center-left-hot-search-title">
<span>热门搜索</span>
<span>
<i className="icon-change"></i>
<span>换一批</span>
</span>
</div>
<div className="header_center-left-hot-search-content">
<span>考研</span>
<span>慢死人</span>
<span>悦心</span>
<span>一致</span>
<span>是的</span>
<span>jsliang</span>
</div>
</div>
</div>
</div>
<div className="header_center-right">
<div className="nav-item header_right-center-setting">
<span>Aa</span>
</div>
<div className="nav-item header_right-center-login">
<span>登录</span>
</div>
</div>
</div>
<div className="header_right nav-item">
<span className="header_right-register">注册</span>
<span className="header_right-write nav-item">
<i className="icon icon-write"></i>
<span>写文章</span>
</span>
</div>
</header>
)
}
}
const mapStateToProps = (state) => {
return {
inputBlur: state.get('header').get('inputBlur')
}
}
const mapDispathToProps = (dispatch) => {
return {
searchFocusOrBlur() {
dispatch(actionCreators.searchFocusOrBlur());
}
}
}
export default connect(mapStateToProps, mapDispathToProps)(Header);
потом, Так как наши данные симулируются из интерфейса, и как упоминалось в предыдущей статье, если мы хотим управлять кодом интерфейса, лучше всего использовать Redux-Thunk и Redux-Saga, здесь мы используем Redux-Thunk:
- Установите редукционный преобразователь:
cnpm i redux-thunk -S
- Установить аксиомы:
cnpm i axios -S
Здесь нам нужно знать, что конфигурация приложения create-react-app содержит Node.js, поэтому мы можем полагаться на данные Mock при разработке с помощью Node.js.
Приступаем к разработке:
src/store/index.js
Сведения о коде
// 2. 引入 redux 的 applyMiddleware,进行多中间件的使用
import { createStore, compose, applyMiddleware } from 'redux';
// 1. 引入 redux-thunk
import thunk from 'redux-thunk';
import reducer from './reducer';
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
// 3. 通过 applyMiddleware 同时使用 redux-thunk 和 redux-dev-tools
const store = createStore(reducer, composeEnhancers(
applyMiddleware(thunk)
));
export default store;
- Внедрить редукционный преобразователь
- Представляем редукс
applyMiddleware
, чтобы использовать несколько промежуточных программ - пройти через
applyMiddleware
Использование как redux-thunk, так и redux-dev-tools
Таким образом, мы можем нормально использовать redux-thunk.
- src/common/header/index.js
Сведения о коде
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { CSSTransition } from 'react-transition-group';
import './index.css';
import { actionCreators } from './store';
import homeImage from '../../resources/img/header-home.png';
class Header extends Component {
render() {
return (
<header>
<div className="header_left">
<a href="/">
<img alt="首页" src={homeImage} className="header_left-img" />
</a>
</div>
<div className="header_center">
<div className="header_center-left">
<div className="nav-item header_center-left-home">
<i className="icon icon-home"></i>
<span>首页</span>
</div>
<div className="nav-item header_center-left-download">
<i className="icon icon-download"></i>
<span>下载App</span>
</div>
<div className="nav-item header_center-left-search">
<CSSTransition
in={this.props.inputBlur}
timeout={200}
classNames="slide"
>
<input
className={this.props.inputBlur ? 'input-nor-active' : 'input-active'}
placeholder="搜索"
onFocus={this.props.searchFocusOrBlur}
onBlur={this.props.searchFocusOrBlur}
/>
</CSSTransition>
<i className={this.props.inputBlur ? 'icon icon-search' : 'icon icon-search icon-active'}></i>
<div className={this.props.inputBlur ? 'display-hide header_center-left-hot-search' : 'display-show header_center-left-hot-search'}>
<div className="header_center-left-hot-search-title">
<span>热门搜索</span>
<span>
<i className="icon-change"></i>
<span>换一批</span>
</span>
</div>
<div className="header_center-left-hot-search-content">
{/* 15. 遍历输出该数据 */}
{
this.props.list.map((item) => {
return <span key={item}>{item}</span>
})
}
</div>
</div>
</div>
</div>
<div className="header_center-right">
<div className="nav-item header_right-center-setting">
<span>Aa</span>
</div>
<div className="nav-item header_right-center-login">
<span>登录</span>
</div>
</div>
</div>
<div className="header_right nav-item">
<span className="header_right-register">注册</span>
<span className="header_right-write nav-item">
<i className="icon icon-write"></i>
<span>写文章</span>
</span>
</div>
</header>
)
}
}
const mapStateToProps = (state) => {
return {
inputBlur: state.get('header').get('inputBlur'),
// 14. 获取 reducer.js 中的 list 数据
list: state.get('header').get('list')
}
}
const mapDispathToProps = (dispatch) => {
return {
searchFocusOrBlur() {
// 4. 派发 action 到 actionCreators.js 中的 getList() 方法
dispatch(actionCreators.getList());
dispatch(actionCreators.searchFocusOrBlur());
}
}
}
export default connect(mapStateToProps, mapDispathToProps)(Header);
- src/common/header/store/actionCreators.js
Сведения о коде
import * as actionTypes from './actionTypes'
// 7. 引入 axios
import axios from 'axios';
// 11. 引入 immutable 的类型转换
import { fromJS } from 'immutable';
export const searchFocusOrBlur = () => ({
type: actionTypes.SEARCH_FOCUS_OR_BLUR
})
// 10. 定义 action,接受参数 data,同时因为我们使用了 Immutable,所以需要将获取的数据转换为 immutable 类型
const changeList = (data) => ({
type: actionTypes.GET_LIST,
data: fromJS(data)
})
// 5. 编写 getList 的 action,由于需要 actionTypes 中定义,所以前往 actionTypes.js 中新增
export const getList = () => {
return (dispatch) => {
// 8. 调用 create-react-app 中提供的 Node 服务器,从而 mock 数据
axios.get('/api/headerList.json').then( (res) => {
if(res.data.code === 0) {
const data = res.data.list;
// 由于数据太多,我们限制数据量为 15 先
data.length = 15;
// 12. 派发 changeList 类型
dispatch(changeList(data));
}
}).catch( (error) => {
console.log(error);
});
}
}
- src/common/header/store/actionTypes.js
Сведения о коде
export const SEARCH_FOCUS_OR_BLUR = 'header/search_focus_or_blur';
// 6. 新增 actionType
export const GET_LIST = 'header/get_list';
- src/common/header/store/reducer.js
Сведения о коде
import * as actionTypes from './actionTypes'
import { fromJS } from 'immutable';
const defaultState = fromJS({
inputBlur: true,
// 9. 给 header 下的 reducer.js 提供存储数据的地方
list: []
});
export default (state = defaultState, action) => {
if(action.type === actionTypes.SEARCH_FOCUS_OR_BLUR) {
return state.set('inputBlur', !state.get('inputBlur'));
}
// 13. 判断 actionTypes 是否为 GET_LIST,如果是则执行该 action
if(action.type === actionTypes.GET_LIST) {
return state.set('list', action.data);
}
return state;
}
- public/api/headerList.json
Сведения о коде
{
"code": 0,
"list": ["区块链","小程序","vue","毕业","PHP","故事","flutter","理财","美食","投稿","手帐","书法","PPT","穿搭","打碗碗花","简书","姥姥的澎湖湾","设计","创业","交友","籽盐","教育","思维导图","疯哥哥","梅西","时间管理","golang","连载","自律","职场","考研","慢世人","悦欣","一纸vr","spring","eos","足球","程序员","林露含","彩铅","金融","木风杂谈","日更","成长","外婆是方言","docker"]
}
Через следующие шаги:
- распространять
action
в actionCreators.jsgetList()
метод - записывать
getList
изaction
, В связи с необходимостьюactionTypes
определен в, поэтому перейдите к ActionTypes.js и добавить - добавить тип действия
- импортировать аксиомы
- Смоделируйте данные, вызвав сервер Node, предоставленный в приложении create-реагировать
- Укажите место для хранения данных для reducer.js под заголовком
- определение
action
, который принимает параметрыdata
, а поскольку мы используем Immutable, нам нужно преобразовать полученные данные вimmutable
Типы - Знакомство с преобразованием типов Immutable
- распространять
changeList
Типы - судить
actionTypes
ЭтоGET_LIST
, если да, то выполнитьaction
- Получите редуктор.js в
list
данные - цикл по данным
Таким образом, мы успешно получаем данные, предоставленные макетом:
Четырнадцать оптимизаций кода
- Используется в редукторе.js
switch...case...
заменятьif...
утверждение.
src/common/header/store/reducer.js
Сведения о коде
import * as actionTypes from './actionTypes'
import { fromJS } from 'immutable';
const defaultState = fromJS({
inputBlur: true,
list: []
});
export default (state = defaultState, action) => {
switch(action.type) {
case actionTypes.SEARCH_FOCUS_OR_BLUR:
return state.set('inputBlur', !state.get('inputBlur'));
case actionTypes.GET_LIST:
return state.set('list', action.data);
default:
return state;
}
}
15. Решение проблем, оставшихся от истории
Здесь мы решаем проблему, оставшуюся от истории: когда мы теряем фокус на поле ввода, наш модуль [Популярный поиск] исчезает, поэтому мы не можем видеть эффект нажатия кнопки [Изменить], поэтому нам нужно изменить следующее. кода, когда наша мышь находится в [горячем модуле], этот модуль не исчезнет, когда наша мышь не в фокусе и мышь не находится в горячем модуле, горячий модуль исчезнет.
- src/common/header/store/reducer.js
Сведения о коде
import * as actionTypes from './actionTypes'
import { fromJS } from 'immutable';
const defaultState = fromJS({
inputFocus: false,
// 1. 设置鼠标移动到热门模块为 false
mouseInHot: false,
list: [],
});
export default (state = defaultState, action) => {
switch(action.type) {
case actionTypes.SEARCH_FOCUS:
return state.set('inputFocus', true);
case actionTypes.SEARCH_BLUR:
return state.set('inputFocus', false);
case actionTypes.GET_LIST:
return state.set('list', action.data);
// 6. 在 reducer.js 中判断这两个 action 执行设置 mouseInHot
case actionTypes.ON_MOUSE_ENTER_HOT:
return state.set('mouseInHot', true);
case actionTypes.ON_MOUSE_LEAVE_HOT:
return state.set('mouseInHot', false);
default:
return state;
}
}
- src/common/header/index.js
Сведения о коде
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { CSSTransition } from 'react-transition-group';
import './index.css';
import { actionCreators } from './store';
import homeImage from '../../resources/img/header-home.png';
class Header extends Component {
render() {
return (
<header>
<div className="header_left">
<a href="/">
<img alt="首页" src={homeImage} className="header_left-img" />
</a>
</div>
<div className="header_center">
<div className="header_center-left">
<div className="nav-item header_center-left-home">
<i className="icon icon-home"></i>
<span>首页</span>
</div>
<div className="nav-item header_center-left-download">
<i className="icon icon-download"></i>
<span>下载App</span>
</div>
<div className="nav-item header_center-left-search">
<CSSTransition
in={this.props.inputFocus}
timeout={200}
classNames="slide"
>
<input
className={this.props.inputFocus ? 'input-active' : 'input-nor-active'}
placeholder="搜索"
onFocus={this.props.searchFocus}
onBlur={this.props.searchBlur}
/>
</CSSTransition>
<i className={this.props.inputFocus ? 'icon icon-search icon-active' : 'icon icon-search'}></i>
{/* 8. 在判断中加多一个 this.props.mouseInHot,这样只要有一个为 true,它就不会消失 */}
<div
className={this.props.inputFocus || this.props.mouseInHot ? 'display-show header_center-left-hot-search' : 'display-hide header_center-left-hot-search'}
// 2. 设置移入为 onMouseEnterHot,移出为 onMouseLeaveHot
onMouseEnter={this.props.onMouseEnterHot}
onMouseLeave={this.props.onMouseLeaveHot}
>
<div className="header_center-left-hot-search-title">
<span>热门搜索</span>
<span>
<i className="icon-change"></i>
<span>换一批</span>
</span>
</div>
<div className="header_center-left-hot-search-content">
{
this.props.list.map((item) => {
return <span key={item}>{item}</span>
})
}
</div>
</div>
</div>
</div>
<div className="header_center-right">
<div className="nav-item header_right-center-setting">
<span>Aa</span>
</div>
<div className="nav-item header_right-center-login">
<span>登录</span>
</div>
</div>
</div>
<div className="header_right nav-item">
<span className="header_right-register">注册</span>
<span className="header_right-write nav-item">
<i className="icon icon-write"></i>
<span>写文章</span>
</span>
</div>
</header>
)
}
}
const mapStateToProps = (state) => {
return {
inputFocus: state.get('header').get('inputFocus'),
list: state.get('header').get('list'),
// 7. 在 index.js 中获取
mouseInHot: state.get('header').get('mouseInHot'),
}
}
const mapDispathToProps = (dispatch) => {
return {
searchFocus() {
dispatch(actionCreators.getList());
dispatch(actionCreators.searchFocus());
},
searchBlur() {
dispatch(actionCreators.searchBlur());
},
// 3. 定义 onMouseEnterHot 和 onMouseLeaveHot 方法
onMouseEnterHot() {
dispatch(actionCreators.onMouseEnterHot());
},
onMouseLeaveHot() {
dispatch(actionCreators.onMouseLeaveHot());
},
}
}
export default connect(mapStateToProps, mapDispathToProps)(Header);
- src/common/header/store/actionCreators.js
Сведения о коде
import * as actionTypes from './actionTypes'
import axios from 'axios';
import { fromJS } from 'immutable';
export const searchFocus = () => ({
type: actionTypes.SEARCH_FOCUS
})
export const searchBlur = () => ({
type: actionTypes.SEARCH_BLUR
})
// 4. 在 actionCreators.js 中定义这两个方法:onMouseEnterHot 和 onMouseLeaveHot
export const onMouseEnterHot = () => ({
type: actionTypes.ON_MOUSE_ENTER_HOT,
})
export const onMouseLeaveHot = () => ({
type: actionTypes.ON_MOUSE_LEAVE_HOT,
})
export const getList = () => {
return (dispatch) => {
axios.get('/api/headerList.json').then( (res) => {
if(res.data.code === 0) {
const data = res.data.list;
// 由于数据太多,我们限制数据量为 15 先
data.length = 15;
dispatch(changeList(data));
}
}).catch( (error) => {
console.log(error);
});
}
}
const changeList = (data) => ({
type: actionTypes.GET_LIST,
data: fromJS(data)
})
- src/common/header/store/actionTypes.js
Сведения о коде
export const SEARCH_FOCUS = 'header/search_focus';
export const SEARCH_BLUR = 'header/search_blur';
export const GET_LIST = 'header/get_list';
// 5. 在 actionTypes.js 中新增 action 类型
export const ON_MOUSE_ENTER_HOT = 'header/on_mouse_enter_hot';
export const ON_MOUSE_LEAVE_HOT = 'header/on_mouse_leave_hot';
Сначала рассмотрим реализацию:
Затем смотрим на логику реализации:
- В reducer.js установите перемещение мыши к горячему модулю, как
false
- В index.js установите въезд как
onMouseEnterHot
, удалено какonMouseLeaveHot
- в index.js
mapDispathToProps
определениеonMouseEnterHot
а такжеonMouseLeaveHot
метод - Определите эти два метода в actionCreators.js:
onMouseEnterHot
а такжеonMouseLeaveHot
- Добавлено в actionTypes.js
action
Типы - Оцените этих двоих в reducer.js
action
Выполнить настройкиmouseInHot
- в index.js
mapStateToProps
ПолучатьmouseInHot
- Добавьте еще одно к суждению в index.js
this.props.mouseInHot
, так что пока существуетtrue
, это не пройдет
Примечание: из-за ранее установленного
this.props.inputFoucsOrBlur
Это приведет к тому, что интерфейс будет вызываться один раз и для фокусировки, и для расфокусировки, а логика будет более сложной и подверженной ошибкам, поэтому здесь мы изменили ее и разделили на две части: фокусировку и расфокусировку.
Реализация шестнадцати функций: изменение по одному
Начнем с функции замены:
- src/common/header/store/reducer.js
Сведения о коде
import * as actionTypes from './actionTypes'
import { fromJS } from 'immutable';
const defaultState = fromJS({
inputFocus: false,
mouseInHot: false,
list: [],
// 1. 在 reducer.js 中设置页数和总页数
page: 1,
totalPage: 1,
});
export default (state = defaultState, action) => {
switch(action.type) {
case actionTypes.SEARCH_FOCUS:
return state.set('inputFocus', true);
case actionTypes.SEARCH_BLUR:
return state.set('inputFocus', false);
case actionTypes.GET_LIST:
// 4. 我们通过 merge 方法同时设置多个 state 值
return state.merge({
list: action.data,
totalPage: action.totalPage
});
case actionTypes.ON_MOUSE_ENTER_HOT:
return state.set('mouseInHot', true);
case actionTypes.ON_MOUSE_LEAVE_HOT:
return state.set('mouseInHot', false);
// 11. 判断 action 类型,并进行设置
case actionTypes.CHANGE_PAGE:
return state.set('page', action.page + 1);
default:
return state;
}
}
- src/common/header/store/actionCreators.js
Сведения о коде
import * as actionTypes from './actionTypes'
import axios from 'axios';
import { fromJS } from 'immutable';
export const searchFocus = () => ({
type: actionTypes.SEARCH_FOCUS
})
export const searchBlur = () => ({
type: actionTypes.SEARCH_BLUR
})
export const onMouseEnterHot = () => ({
type: actionTypes.ON_MOUSE_ENTER_HOT,
})
export const onMouseLeaveHot = () => ({
type: actionTypes.ON_MOUSE_LEAVE_HOT,
})
export const getList = () => {
return (dispatch) => {
axios.get('/api/headerList.json').then( (res) => {
if(res.data.code === 0) {
const data = res.data.list;
// 2. 由于数据太多,我们之前限制数据量为 15,这里我们去掉该行代码
// data.length = 15;
dispatch(changeList(data));
}
}).catch( (error) => {
console.log(error);
});
}
}
const changeList = (data) => ({
type: actionTypes.GET_LIST,
data: fromJS(data),
// 3. 我们在这里计算总页数
totalPage: Math.ceil(data.length / 10)
})
// 9. 定义 changePage 方法
export const changePage = (page) => ({
type: actionTypes.CHANGE_PAGE,
page: page,
})
- src/common/header/index.js
Сведения о коде
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { CSSTransition } from 'react-transition-group';
import './index.css';
import { actionCreators } from './store';
import homeImage from '../../resources/img/header-home.png';
class Header extends Component {
render() {
return (
<header>
<div className="header_left">
<a href="/">
<img alt="首页" src={homeImage} className="header_left-img" />
</a>
</div>
<div className="header_center">
<div className="header_center-left">
<div className="nav-item header_center-left-home">
<i className="icon icon-home"></i>
<span>首页</span>
</div>
<div className="nav-item header_center-left-download">
<i className="icon icon-download"></i>
<span>下载App</span>
</div>
<div className="nav-item header_center-left-search">
<CSSTransition
in={this.props.inputFocus}
timeout={200}
classNames="slide"
>
<input
className={this.props.inputFocus ? 'input-active' : 'input-nor-active'}
placeholder="搜索"
onFocus={this.props.searchFocus}
onBlur={this.props.searchBlur}
/>
</CSSTransition>
<i className={this.props.inputFocus ? 'icon icon-search icon-active' : 'icon icon-search'}></i>
<div
className={this.props.inputFocus || this.props.mouseInHot ? 'display-show header_center-left-hot-search' : 'display-hide header_center-left-hot-search'}
onMouseEnter={this.props.onMouseEnterHot}
onMouseLeave={this.props.onMouseLeaveHot}
>
<div className="header_center-left-hot-search-title">
<span>热门搜索</span>
{/* 7. 进行换页功能实现,传递参数 page 和 totalPage */}
<span onClick={() => this.props.changePage(this.props.page, this.props.totalPage)}>
<i className="icon-change"></i>
<span className="span-change">换一批</span>
</span>
</div>
<div className="header_center-left-hot-search-content">
{
// 6. 在 index.js 中进行计算:
// 一开始显示 0-9 共 10 条,换页的时候显示 10-19 ……以此类推
this.props.list.map((item, index) => {
if(index >= (this.props.page - 1) * 10 && index < this.props.page * 10) {
return <span key={item}>{item}</span>
} else {
return '';
}
})
}
</div>
</div>
</div>
</div>
<div className="header_center-right">
<div className="nav-item header_right-center-setting">
<span>Aa</span>
</div>
<div className="nav-item header_right-center-login">
<span>登录</span>
</div>
</div>
</div>
<div className="header_right nav-item">
<span className="header_right-register">注册</span>
<span className="header_right-write nav-item">
<i className="icon icon-write"></i>
<span>写文章</span>
</span>
</div>
</header>
)
}
}
const mapStateToProps = (state) => {
return {
inputFocus: state.get('header').get('inputFocus'),
list: state.get('header').get('list'),
mouseInHot: state.get('header').get('mouseInHot'),
// 5. 在 index.js 中 mapStateToProps 获取数据
page: state.get('header').get('page'),
totalPage: state.get('header').get('totalPage'),
}
}
const mapDispathToProps = (dispatch) => {
return {
searchFocus() {
dispatch(actionCreators.getList());
dispatch(actionCreators.searchFocus());
},
searchBlur() {
dispatch(actionCreators.searchBlur());
},
onMouseEnterHot() {
dispatch(actionCreators.onMouseEnterHot());
},
onMouseLeaveHot() {
dispatch(actionCreators.onMouseLeaveHot());
},
// 8. 调用 changePage 方法
changePage(page, totalPage) {
if(page === totalPage) {
page = 1;
dispatch(actionCreators.changePage(page));
} else {
dispatch(actionCreators.changePage(page));
}
}
}
}
export default connect(mapStateToProps, mapDispathToProps)(Header);
- src/common/header/store/actionTypes.js
Сведения о коде
export const SEARCH_FOCUS = 'header/search_focus';
export const SEARCH_BLUR = 'header/search_blur';
export const GET_LIST = 'header/get_list';
export const ON_MOUSE_ENTER_HOT = 'header/on_mouse_enter_hot';
export const ON_MOUSE_LEAVE_HOT = 'header/on_mouse_leave_hot';
// 10. 定义 action
export const CHANGE_PAGE = 'header/change_page';
На данный момент наши идеи кода таковы:
- Установка количества страниц в reducer.js
page
и всего страницtotalPage
- В actionCreators.js мы ранее ограничивали количество данных до 15 из-за слишком большого количества данных, здесь мы удаляем эту строку кода
- Подсчитайте общее количество страниц здесь, в actionCreators.js
- Передайте в редукторе.js
merge
метод установки несколькихstate
стоимость - в index.js
mapStateToProps
получить данные - Расчет в index.js: 10 пунктов 0-9 выводятся в начале, 10-19 выводятся при смене страницы... и так далее
- Реализовать функцию смены страницы в index.js и передать параметры
page
а такжеtotalPage
- Вызывается в index.js
changePage
метод, чтобы определить, следует ли сбросить на первую страницу, иdispatch
метод - Определено в actionCreators.js
changePage
метод - Определено в actionTypes.js
action
- Судя по редуктору.js
action
введите и установите
Таким образом, мы реализовали функцию замены:
Оптимизация семнадцати функций
17.1 Изменение поворота значка
src/common/header/index.css
Сведения о коде
header {
width: 100%;
height: 58px;
display: flex;
align-items: center;
border-bottom: 1px solid #ccc;
font-size: 17px;
}
/* 头部左边 */
.header_left-img {
width: 100px;
height: 56px;
}
/* 头部中间 */
.header_center {
width: 1000px;
margin: 0 auto;
display: flex;
justify-content: space-between;
}
.nav-item {
margin-right: 30px;
display: flex;
align-items: center;
}
/* 头部中间左部 */
.header_center-left {
display: flex;
}
/* 头部中间左部 - 首页 */
.header_center-left-home {
color: #ea6f5a;
}
/* 头部中间左部 - 搜索框 */
.header_center-left-search {
position: relative;
}
.slide-enter {
transition: all .2s ease-out;
}
.slide-enter-active {
width: 280px;
}
.slide-exit {
transition: all .2s ease-out;
}
.silde-exit-active {
width: 240px;
}
.header_center-left-search input {
width: 240px;
padding: 0 45px 0 20px;
height: 38px;
font-size: 14px;
border: 1px solid #eee;
border-radius: 40px;
background: #eee;
}
.header_center-left-search .input-active {
width: 280px;
}
.header_center-left-search .icon-search {
position: absolute;
top: 8px;
right: 10px;
}
.header_center-left-search .icon-active {
padding: 3px;
top: 4px;
border-radius: 15px;
border: 1px solid #ea6f5a;
}
/* 头部中间左部 - 热搜 */
.header_center-left-search .icon-active:hover {
cursor: pointer;
}
.header_center-left-hot-search:before {
content: "";
left: 27px;
width: 10px;
height: 10px;
transform: rotate(45deg);
top: -5px;
z-index: -1;
position: absolute;
background-color: #fff;
box-shadow: 0 0 8px rgba(0,0,0,.2);
}
.header_center-left-hot-search {
position: absolute;
width: 250px;
left: 0;
top: 125%;
padding: 15px;
font-size: 14px;
background: #fff;
border-radius: 4px;
box-shadow: 0 0 8px rgba(0, 0, 0, 0.2);
}
.header_center-left-hot-search-title {
display: flex;
justify-content: space-between;
color: #969696;
}
.header_center-left-hot-search-change {
display: flex;
justify-content: space-between;
align-items: center;
}
.icon-change {
display: inline-block;
width: 20px;
height: 14px;
background: url('../../resources/img/icon-change.png') no-repeat center;
background-size: 100%;
/* 1. 在 index.css 中添加动画 */
transition: all .2s ease-in;
transform-origin: center center;
}
.icon-change:hover {
cursor: pointer;
}
.span-change:hover {
cursor: pointer;
}
.header_center-left-hot-search-content span {
display: inline-block;
margin-top: 10px;
margin-right: 10px;
padding: 2px 6px;
font-size: 12px;
color: #787878;
border: 1px solid #ddd;
border-radius: 3px;
}
.header_center-left-hot-search-content span:hover {
cursor: pointer;
}
/* 头部中间右部 */
.header_center-right {
display: flex;
color: #969696;
}
/* 头部右边 */
.header_right-register, .header_right-write {
width: 80px;
text-align: center;
height: 38px;
line-height: 38px;
border: 1px solid rgba(236,97,73,.7);
border-radius: 20px;
font-size: 15px;
color: #ea6f5a;
background-color: transparent;
}
.header_right-write {
margin-left: 10px;
padding-left: 10px;
margin-right: 0px;
color: #fff;
background-color: #ea6f5a;
}
src/common/header/index.js
Сведения о коде
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { CSSTransition } from 'react-transition-group';
import './index.css';
import { actionCreators } from './store';
import homeImage from '../../resources/img/header-home.png';
class Header extends Component {
render() {
return (
<header>
<div className="header_left">
<a href="/">
<img alt="首页" src={homeImage} className="header_left-img" />
</a>
</div>
<div className="header_center">
<div className="header_center-left">
<div className="nav-item header_center-left-home">
<i className="icon icon-home"></i>
<span>首页</span>
</div>
<div className="nav-item header_center-left-download">
<i className="icon icon-download"></i>
<span>下载App</span>
</div>
<div className="nav-item header_center-left-search">
<CSSTransition
in={this.props.inputFocus}
timeout={200}
classNames="slide"
>
<input
className={this.props.inputFocus ? 'input-active' : 'input-nor-active'}
placeholder="搜索"
onFocus={this.props.searchFocus}
onBlur={this.props.searchBlur}
/>
</CSSTransition>
<i className={this.props.inputFocus ? 'icon icon-search icon-active' : 'icon icon-search'}></i>
<div
className={this.props.inputFocus || this.props.mouseInHot ? 'display-show header_center-left-hot-search' : 'display-hide header_center-left-hot-search'}
onMouseEnter={this.props.onMouseEnterHot}
onMouseLeave={this.props.onMouseLeaveHot}
>
<div className="header_center-left-hot-search-title">
<span>热门搜索</span>
{/* 2. 在 index.js 中给 i 标签添加 ref,并通过 changePage 方法传递过去 */}
<span onClick={() => this.props.changePage(this.props.page, this.props.totalPage, this.spinIcon)}>
<i className="icon-change" ref={(icon) => {this.spinIcon = icon}}></i>
<span className="span-change">换一批</span>
</span>
</div>
<div className="header_center-left-hot-search-content">
{
this.props.list.map((item, index) => {
if(index >= (this.props.page - 1) * 10 && index < this.props.page * 10) {
return <span key={item}>{item}</span>
} else {
return '';
}
})
}
</div>
</div>
</div>
</div>
<div className="header_center-right">
<div className="nav-item header_right-center-setting">
<span>Aa</span>
</div>
<div className="nav-item header_right-center-login">
<span>登录</span>
</div>
</div>
</div>
<div className="header_right nav-item">
<span className="header_right-register">注册</span>
<span className="header_right-write nav-item">
<i className="icon icon-write"></i>
<span>写文章</span>
</span>
</div>
</header>
)
}
}
const mapStateToProps = (state) => {
return {
inputFocus: state.get('header').get('inputFocus'),
list: state.get('header').get('list'),
mouseInHot: state.get('header').get('mouseInHot'),
page: state.get('header').get('page'),
totalPage: state.get('header').get('totalPage'),
}
}
const mapDispathToProps = (dispatch) => {
return {
searchFocus() {
dispatch(actionCreators.getList());
dispatch(actionCreators.searchFocus());
},
searchBlur() {
dispatch(actionCreators.searchBlur());
},
onMouseEnterHot() {
dispatch(actionCreators.onMouseEnterHot());
},
onMouseLeaveHot() {
dispatch(actionCreators.onMouseLeaveHot());
},
changePage(page, totalPage, spinIcon) {
// 3. 在 index.js 中设置它原生 DOM 的 CSS 属性
if(spinIcon.style.transform === 'rotate(360deg)') {
spinIcon.style.transform = 'rotate(0deg)';
} else {
spinIcon.style.transform = 'rotate(360deg)';
}
if(page === totalPage) {
page = 1;
dispatch(actionCreators.changePage(page));
} else {
dispatch(actionCreators.changePage(page));
}
}
}
}
export default connect(mapStateToProps, mapDispathToProps)(Header);
Здесь мы реализуем поворот значка в три шага:
- Добавить анимацию в index.css
- в index.js дать
i
добавить тегиref
, и пройтиchangePage
метод передан - Установите собственные свойства CSS DOM в index.js.
Эффект следующий:
17.2 Не зацикливайтесь на повторяющихся запросах
В коде каждый раз, когда мы фокусируемся, мы будем запрашивать данные, поэтому нам нужноlist
значение, чтобы определить, запрашивать ли данные:
src/common/header/index.js
Сведения о коде
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { CSSTransition } from 'react-transition-group';
import './index.css';
import { actionCreators } from './store';
import homeImage from '../../resources/img/header-home.png';
class Header extends Component {
render() {
return (
<header>
<div className="header_left">
<a href="/">
<img alt="首页" src={homeImage} className="header_left-img" />
</a>
</div>
<div className="header_center">
<div className="header_center-left">
<div className="nav-item header_center-left-home">
<i className="icon icon-home"></i>
<span>首页</span>
</div>
<div className="nav-item header_center-left-download">
<i className="icon icon-download"></i>
<span>下载App</span>
</div>
<div className="nav-item header_center-left-search">
<CSSTransition
in={this.props.inputFocus}
timeout={200}
classNames="slide"
>
<input
className={this.props.inputFocus ? 'input-active' : 'input-nor-active'}
placeholder="搜索"
// 1. 给 searchFocus 传递 list
onFocus={() => this.props.searchFocus(this.props.list)}
onBlur={this.props.searchBlur}
/>
</CSSTransition>
<i className={this.props.inputFocus ? 'icon icon-search icon-active' : 'icon icon-search'}></i>
<div
className={this.props.inputFocus || this.props.mouseInHot ? 'display-show header_center-left-hot-search' : 'display-hide header_center-left-hot-search'}
onMouseEnter={this.props.onMouseEnterHot}
onMouseLeave={this.props.onMouseLeaveHot}
>
<div className="header_center-left-hot-search-title">
<span>热门搜索</span>
<span onClick={() => this.props.changePage(this.props.page, this.props.totalPage, this.spinIcon)}>
<i className="icon-change" ref={(icon) => {this.spinIcon = icon}}></i>
<span className="span-change">换一批</span>
</span>
</div>
<div className="header_center-left-hot-search-content">
{
this.props.list.map((item, index) => {
if(index >= (this.props.page - 1) * 10 && index < this.props.page * 10) {
return <span key={item}>{item}</span>
} else {
return '';
}
})
}
</div>
</div>
</div>
</div>
<div className="header_center-right">
<div className="nav-item header_right-center-setting">
<span>Aa</span>
</div>
<div className="nav-item header_right-center-login">
<span>登录</span>
</div>
</div>
</div>
<div className="header_right nav-item">
<span className="header_right-register">注册</span>
<span className="header_right-write nav-item">
<i className="icon icon-write"></i>
<span>写文章</span>
</span>
</div>
</header>
)
}
}
const mapStateToProps = (state) => {
return {
inputFocus: state.get('header').get('inputFocus'),
list: state.get('header').get('list'),
mouseInHot: state.get('header').get('mouseInHot'),
page: state.get('header').get('page'),
totalPage: state.get('header').get('totalPage'),
}
}
const mapDispathToProps = (dispatch) => {
return {
searchFocus(list) {
// 2. 判断 list 的 size 是不是等于 0,是的话才请求数据(第一次),不是的话则不请求
if(list.size === 0) {
dispatch(actionCreators.getList());
}
dispatch(actionCreators.searchFocus());
},
searchBlur() {
dispatch(actionCreators.searchBlur());
},
onMouseEnterHot() {
dispatch(actionCreators.onMouseEnterHot());
},
onMouseLeaveHot() {
dispatch(actionCreators.onMouseLeaveHot());
},
changePage(page, totalPage, spinIcon) {
if(spinIcon.style.transform === 'rotate(360deg)') {
spinIcon.style.transform = 'rotate(0deg)';
} else {
spinIcon.style.transform = 'rotate(360deg)';
}
if(page === totalPage) {
page = 1;
dispatch(actionCreators.changePage(page));
} else {
dispatch(actionCreators.changePage(page));
}
}
}
}
export default connect(mapStateToProps, mapDispathToProps)(Header);
Здесь мы делаем два шага:
- Дать
searchFocus
передачаlist
- существует
searchFocus
среднее суждениеlist
изsize
Равно ли 0, если есть, то только запрашивать данные (первый раз), если нет, то не запрашивать
Таким образом, мы успешно избегаем концентрации на повторяющихся запросах.
Восемнадцать React Маршрутизация
18.1 Маршрутизация (1)
- Что такое маршрутизация?
Внешняя маршрутизация предназначена для отображения разного контента в соответствии с разными URL-адресами.
- Установите маршруты React:
npm i react-router-dom -S
После установки нам нужно только изменитьsrc/App.js
, вы можете испытать маршрутизацию:
src/App.js
Сведения о коде
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import Header from './common/header';
import store from './store';
// 1. 引入 React 路由的 BrowserRouter 和 Route
import { BrowserRouter, Route } from 'react-router-dom';
class App extends Component {
render() {
return (
<Provider store={store} className="App">
<Header />
{/* 2. 在页面中使用 React 路由 */}
<BrowserRouter>
<Route path="/" exact render={() => <div>HOME</div>}></Route>
<Route path="/detail" exact render={() => <div>DETAIL</div>}></Route>
</BrowserRouter>
</Provider>
);
}
}
export default App;
Здесь нам нужно сделать всего два шага:
- Представляя Ract Rounting
BrowserRouter
а такжеRoute
- Использование маршрутизации React на страницах
Таким образом, мы реализовали маршрутизацию:
18.2 Маршрутизация (2)
- Создайте новую папку pages в src, а затем создайте новые папки и файлы в этой папке:
- src/pages/detail/index.js
- src/pages/home/index.js
- Их содержание следующее:
src/pages/detail/index.js
Сведения о коде
import React, { Component } from 'react'
class Detail extends Component {
render() {
return (
<div>Detail</div>
)
}
}
export default Detail;
src/pages/home/index.js
Сведения о коде
import React, { Component } from 'react'
class Home extends Component {
render() {
return (
<div>Home</div>
)
}
}
export default Home;
Имея опыт работы с заголовками, мы должны знать, что хотим ввести путь в URLlocalhost:3000
, получить доступ к домашнему компоненту; при входеlocalhost:3000/detail
, доступ к компоненту сведений.
- На данный момент нам нужно только изменить
src/App.js
, вы можете достичь цели:
src/App.js
Сведения о коде
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import Header from './common/header';
import store from './store';
import { BrowserRouter, Route } from 'react-router-dom';
// 1. 引入 Home、Detail 组件
import Home from './pages/home';
import Detail from './pages/detail';
class App extends Component {
render() {
return (
<Provider store={store} className="App">
<Header />
<BrowserRouter>
{/* 2. 在页面中引用组件 */}
<Route path="/" exact component={Home}></Route>
<Route path="/detail" exact component={Detail}></Route>
</BrowserRouter>
</Provider>
);
}
}
export default App;
Теперь при переключении маршрута мы видим неиспользуемые страницы, а также можем модифицировать эти страницы, отредактировав соответствующий index.js.
Реализация девятнадцати страниц: дополнительная панель навигации
Из-за предыдущего опыта программирования мы не будем здесь говорить ерунду и непосредственно реализуем это.
«Цзяньшу» серьезно угрожает порядку распространения информации в Интернете из-за нарушения соответствующих законов и правил, таких как «Закон о сетевой безопасности», «Административные меры в отношении информационных служб Интернета», «Административные правила информационных служб Интернет-новостей» и других соответствующих Законы и правила С 0:00 13 до 00:00 19 апреля обновление контента на стороне ПК будет приостановлено, а контент на всех платформах будет полностью и полностью исправлен.
Ни в коем случае, я изначально хотел продолжить писать в соответствии с домашней страницей Jianshu, но у меня возникла проблема с Jianshu, поэтому мне пришлось использовать домашнюю страницу и страницу сведений о Nuggets, чтобы добиться этого.
Мы делим домашнюю страницу Nuggets на 3 модуля: TopNav вверху, LeftList слева и RightRecommend справа. Итак, мы создаем новый каталог компонентов в разделе home для хранения этих трех компонентов. В то же время при разработке common/header мы также знаем, что нам также нужна папка store для хранения reducer.js и т.д.:
- pages
- detail
- index.js
- home
- components
- LeftList.js
- RightRecommend.js
- TopNav.js
- store
- actionCreators.js
- actionTypes.js
- index.js
- reducer.js
- index.css
- index.js
- src/index.css
Сведения о коде
body {
background: #f4f5f5;
}
- src/App.js
Сведения о коде
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import Header from './common/header';
import store from './store';
import { BrowserRouter, Route } from 'react-router-dom';
import Home from './pages/home';
import Detail from './pages/detail';
class App extends Component {
render() {
return (
<Provider store={store} className="App">
<Header />
<BrowserRouter>
<Route path="/" exact component={Home}></Route>
<Route path="/detail" exact component={Detail}></Route>
</BrowserRouter>
</Provider>
);
}
}
export default App;
- src/common/header/index.css
Сведения о коде
header {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 58px;
display: flex;
align-items: center;
border-bottom: 1px solid #f1f1f1;
font-size: 17px;
background: #fff;
}
/* 头部左边 */
.header_left-img {
width: 100px;
height: 56px;
}
/* 头部中间 */
.header_center {
width: 1000px;
margin: 0 auto;
display: flex;
justify-content: space-between;
}
.nav-item {
margin-right: 30px;
display: flex;
align-items: center;
}
/* 头部中间左部 */
.header_center-left {
display: flex;
}
/* 头部中间左部 - 首页 */
.header_center-left-home {
color: #ea6f5a;
}
/* 头部中间左部 - 搜索框 */
.header_center-left-search {
position: relative;
}
.slide-enter {
transition: all .2s ease-out;
}
.slide-enter-active {
width: 280px;
}
.slide-exit {
transition: all .2s ease-out;
}
.silde-exit-active {
width: 240px;
}
.header_center-left-search {
z-index: 999;
}
.header_center-left-search input {
width: 240px;
padding: 0 45px 0 20px;
height: 38px;
font-size: 14px;
border: 1px solid #eee;
border-radius: 40px;
background: #eee;
}
.header_center-left-search .input-active {
width: 280px;
}
.header_center-left-search .icon-search {
position: absolute;
top: 8px;
right: 10px;
}
.header_center-left-search .icon-active {
padding: 3px;
top: 4px;
border-radius: 15px;
border: 1px solid #ea6f5a;
}
/* 头部中间左部 - 热搜 */
.header_center-left-search .icon-active:hover {
cursor: pointer;
}
.header_center-left-hot-search:before {
content: "";
left: 27px;
width: 10px;
height: 10px;
transform: rotate(45deg);
top: -5px;
z-index: -1;
position: absolute;
background-color: #fff;
box-shadow: 0 0 8px rgba(0,0,0,.2);
}
.header_center-left-hot-search {
position: absolute;
width: 250px;
left: 0;
top: 125%;
padding: 15px;
font-size: 14px;
background: #fff;
border-radius: 4px;
box-shadow: 0 0 8px rgba(0, 0, 0, 0.2);
}
.header_center-left-hot-search-title {
display: flex;
justify-content: space-between;
color: #969696;
}
.header_center-left-hot-search-change {
display: flex;
justify-content: space-between;
align-items: center;
}
.icon-change {
display: inline-block;
width: 20px;
height: 14px;
background: url('../../resources/img/icon-change.png') no-repeat center;
background-size: 100%;
transition: all .2s ease-in;
transform-origin: center center;
}
.icon-change:hover {
cursor: pointer;
}
.span-change:hover {
cursor: pointer;
}
.header_center-left-hot-search-content span {
display: inline-block;
margin-top: 10px;
margin-right: 10px;
padding: 2px 6px;
font-size: 12px;
color: #787878;
border: 1px solid #ddd;
border-radius: 3px;
}
.header_center-left-hot-search-content span:hover {
cursor: pointer;
}
/* 头部中间右部 */
.header_center-right {
display: flex;
color: #969696;
}
/* 头部右边 */
.header_right-register, .header_right-write {
width: 80px;
text-align: center;
height: 38px;
line-height: 38px;
border: 1px solid rgba(236,97,73,.7);
border-radius: 20px;
font-size: 15px;
color: #ea6f5a;
background-color: transparent;
}
.header_right-write {
margin-left: 10px;
padding-left: 10px;
margin-right: 0px;
color: #fff;
background-color: #ea6f5a;
}
- src/pages/home/index.js
Сведения о коде
import React, { Component } from 'react';
import LeftList from './components/LeftList';
import RightRecommend from './components/RightRecommend';
import TopNav from './components/TopNav';
import './index.css';
class Home extends Component {
render() {
return (
<div className="container">
<TopNav />
<div className="main-container">
<LeftList />
<RightRecommend />
</div>
</div>
)
}
}
export default Home;
- src/pages/home/index.css
Сведения о коде
/* 主体 */
.container {
width: 960px;
margin: 0 auto;
}
.main-container {
display: flex;
}
/* 顶部 */
.top-nav {
position: fixed;
left: 0;
top: 59px;
width: 100%;
height: 46px;
line-height: 46px;
z-index: 100;
box-shadow: 0 1px 2px 0 rgba(0,0,0,.05);
font-size: 14px;
background: #fff;
}
.top-nav-list {
display: flex;
width: 960px;
margin: auto;
position: relative;
}
.top-nav-list-item a {
height: 100%;
align-items: center;
display: flex;
flex-shrink: 0;
color: #71777c;
padding-right: 12px;
}
.active a {
color: #007fff;
}
.top-nav-list-right {
position: absolute;
top: 0;
right: 0;
}
/* 主内容 */
.main-container {
margin-top: 120px;
}
/* 左侧 */
.left-list {
width: 650px;
height: 1000px;
background: #fff;
}
/* 右侧 */
.right-recommend {
width: 295px;
height: 1000px;
margin-left: 15px;
background: #fff;
}
- src/pages/home/components/TopNav.js
Сведения о коде
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
class TopNav extends Component {
render() {
return (
<div className="top-nav">
<ul className="top-nav-list">
<li className="top-nav-list-item active">
<Link to="tuijian">推荐</Link>
</li>
<li className="top-nav-list-item">
<Link to="guanzhu">关注</Link>
</li>
<li className="top-nav-list-item">
<Link to="houduan">后端</Link>
</li>
<li className="top-nav-list-item">
<Link to="qianduan">前端</Link>
</li>
<li className="top-nav-list-item">
<Link to="anzhuo">Android</Link>
</li>
<li className="top-nav-list-item">
<Link to="ios">IOS</Link>
</li>
<li className="top-nav-list-item">
<Link to="rengongzhineng">人工智能</Link>
</li>
<li className="top-nav-list-item">
<Link to="kaifagongju">开发工具</Link>
</li>
<li className="top-nav-list-item">
<Link to="daimarensheng">代码人生</Link>
</li>
<li className="top-nav-list-item">
<Link to="yuedu">阅读</Link>
</li>
<li className="top-nav-list-item top-nav-list-right">
<Link to="biaoqianguanli">标签管理</Link>
</li>
</ul>
</div>
)
}
}
export default TopNav;
- src/pages/home/components/LeftList.js
Сведения о коде
import React, { Component } from 'react'
class LeftList extends Component {
render() {
return (
<div className="left-list">
左侧
</div>
)
}
}
export default LeftList;
- src/pages/home/components/RightRecommend.js
Сведения о коде
import React, { Component } from 'react'
class RightRecommend extends Component {
render() {
return (
<div className="right-recommend">
右侧
</div>
)
}
}
export default RightRecommend;
В этот момент страница выглядит так:
Реализация двадцати страниц: Главная
20.1 Многоуровневое хранилище ссылок на компоненты
В нашем плане App является основным компонентом, с заголовком | home | подробно ниже, и LeftList | RightRecommend ниже home, так как же App/home/leftList относится к магазину?
src/pages/home/components/LeftList.js
Сведения о коде
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
// 1. 在 LeftList 中引入 react-redux 的 connect
import { connect } from 'react-redux';
import { actionCreators } from '../store';
class LeftList extends Component {
render() {
return (
<div className="left-list">
<div className="left-list-top">
<ul className="left-list-top-left">
<li className="active">
<Link to='remen'>热门</Link>
</li>
<span>|</span>
<li>
<Link to='zuixin'>最新</Link>
</li>
<span>|</span>
<li>
<Link to='pinglun'>评论</Link>
</li>
</ul>
<ul className="left-list-top-right">
<li>
<Link to='benzhouzuire'>本周最热</Link>
</li>
·
<li>
<Link to='benyuezuire'>本月最热</Link>
</li>
·
<li>
<Link to='lishizuire'>历史最热</Link>
</li>
</ul>
</div>
<div className="left-list-container">
{/* 5. 循环输出 props 里面的数据 */}
{
this.props.list.map((item) => {
return (
<div className="left-list-item" key={item.get('id')}>
<div className="left-list-item-tag">
<span className="hot">热</span>·
<span className="special">专栏</span>·
<span>
{
item.get('user').get('username')
}
</span>·
<span>一天前</span>·
<span>
{
item.get('tags').map((tagsItem, index) => {
if (index === 0) {
return tagsItem.get('title');
} else {
return null;
}
})
}
</span>
</div>
<h3 className="left-list-item-title">
<Link to="detail">{item.get('title')}</Link>
</h3>
<div className="left-list-item-interactive">
<span>{item.get('likeCount')}</span>
<span>{item.get('commentsCount')}</span>
</div>
</div>
)
})
}
</div>
</div>
)
}
componentDidMount() {
this.props.getLeftList();
}
}
// 3. 在 LeftList 中定义 mapStateToProps
const mapStateToProps = (state) => {
return {
list: state.get('home').get('leftNav')
}
};
// 4. 在 LeftList 中定义 mapDispathToProps
const mapDispathToProps = (dispatch) => {
return {
getLeftList() {
dispatch(actionCreators.getLeftList());
}
}
};
// 2. 在 LeftList 中使用 connect
export default connect(mapStateToProps, mapDispathToProps)(LeftList);
20.2 Улучшить всю домашнюю страницу
Конечно, если вы просто запустите приведенный выше код, вы обнаружите, что он сообщает об ошибке.
Да потому что это только часть всего кода, поэтому вам нужно его улучшить. Конечно, вы также можете получить весь код напрямую:
В любом случае, конечный результат, которого вы достигнете, выглядит так:
21 Резюме
На данный момент мы завершили разработку домашней страницы.
В этой разработке мы многому научились.
Конечно, послеjsliangЯ тоже ленивый, а в оригинальном видео МООК тоже есть:
- Загрузить больше реализации функций
- Перейти к началу реализации функции
- Разработка страницы подробностей
- Разработка целевой страницы
- Реализация функции аутентификации входа
- Асинхронная загрузка компонентов на одной странице (реактивная загрузка)
- ...
Я не буду перечислять их все здесь, потому чтоjsliangЯ чувствую, что они очень повторяются, нам просто нужно попрактиковаться в следующем проекте, я думаю, мы сможем получить более четкое впечатление. (Конечно, при условии, что выjsliangТак же мотивирован на углубленное изучение)
Итак, мы объявляем об окончании, увидимся в следующей статье!
jsliangРекламный толчок:
Может быть, друг хочет узнать об облачных серверах
Или друг хочет купить облачный сервер
Или маленькому партнеру необходимо обновить облачный сервер
Добро пожаловать, чтобы нажатьПродвижение облачного сервераПроверить!
библиотека документации jsliangЗависит отЛян ЦзюньронгиспользоватьCreative Commons Attribution-NonCommercial-ShareAlike 4.0 Международная лицензияЛицензия.
на основеGitHub.com/l ian Jun Ron…Создание работ выше.
Права на использование, отличные от разрешенных в настоящем Лицензионном соглашении, могут быть получены отCreative Commons.org/licenses/не…получено в.