Научит вас, как реализовать простой личный блог с помощью React

React.js

В процессе изучения React я реализовал личный блог, без сложной реализации и эксплуатации, пригодный для входа ~

Оригинальный адрес:GitHub.com/Axue Temple/Hot Ah…


На самом деле функция этого проекта очень проста, то есть общая домашняя страница, блог, демо, обо мне и другие функции.

Все стили страницы написаны мной, в черно-белом стиле, что может быть немного некрасиво. Но все же CSS самого низкого уровня, готовый к рефакторингу ~

Если есть лучший способ или если мои идеи предвзяты, пожалуйста, сообщите и поправьте меня.

Добро пожаловать в гости:axuebin.com/react-blog

Гитхаб:GitHub.com/Axue Temple/Hot Ah…

предварительный просмотр

титульная страница

страница блога

Страница содержания статьи

Демонстрационная страница

ключевая технология

  • ES6: В проекте используется синтаксис ES6, попробуйте использовать его в процессе написания, могут быть места, которых я не ожидал
  • React
  • React-Router: внешняя маршрутизация
  • React-Redux: управление состоянием
  • вебпак: упаковка
  • Отмечено: рендеринг отметки
  • highlight.js: выделение кода
  • выборка: асинхронный запрос данных
  • eslint: проверка кода
  • antd: Некоторым компонентам лень писать свои собственные. .

Готов к работе

Поскольку это не проект, сгенерированный с использованием скаффолдинга React, все настраивается вручную само по себе. . .

сборщик модулей

упаковано сwebpack 2.6.1, готовы войти в ямуwebpack 3.

Официальная документация:webpack.js.org/

Китайский документ:doc.webpack-china.org/

дляwebpackКонфигурация не слишком знакома, просто простая конфигурация для запуска проекта:

var webpack = require('webpack');
var path = require('path');

module.exports = {
  context: __dirname + '/src',
  entry: "./js/index.js",
  module: {
    loaders: [
      {
        test: /\.js?$/,
        exclude: /(node_modules)/,
        loader: 'babel-loader',
        query: {
          presets: ['react', 'es2015']
        }
      }, {
        test: /\.css$/,
        loader: 'style-loader!css-loader'
      }, {
        test: /\.js$/,
        exclude: /(node_modules)/,
        loader: 'eslint-loader'
      }, {
        test: /\.json$/,
        loader: 'json-loader'
      }
    ]
  },
  output: {
    path: __dirname + "/src/",
    filename: "bundle.js"
  }
}

webpackЕсть несколько важных свойств:entry,module,output,plugins, плагином еще не пользовался, так что настройки нетplugins.

moduleсерединаloaders:

  • babel-loader: преобразовать код в код es5
  • css-loader: обрабатывать такие проблемы, как ссылки на пути в css
  • style-loader: динамически записывать стили в css
  • eslin-loader: используйте eslint

управление пакетами

Управление пакетами все еще используетсяNPM.

Официальная документация:docs.npmjs.com/

  1. npm init
  2. npm install
  3. npm uninstall

оnpm, возможно, также необходимо знатьdependenciesа такжеdevDependenciesРазница, я просто понимаю это так:

  • зависимости: модули, которые необходимо использовать после запуска проекта.
  • devDependencies: модули, необходимые для разработки, но не нужные после запуска проекта.

проверка кода

В проекте используется более популярныйESLintв качестве инструмента проверки кода и использоватьAirbnbправила осмотра.

ESLint:GitHub.com/ESL int/Злые силы…

eslint-config-airbnb:Ууууу, эта лошадь плюс .com/package/evil force…

существуетpackage.jsonЕго можно увидеть вESLintПакет помещается вdevDependenciesНижний, потому что он используется только во время разработки.

использовать

  • существуетwebpackзагрузить в конфигурацииeslint-loader:
module: {
  loaders: [
      {
        test: /\.js$/,
        exclude: /(node_modules)/,
        loader: 'eslint-loader'
      }
    ]
  }
  • Создайте.elintrcдокумент:
{
  "extends": "airbnb",
  "env":{
    "browser": true
  },
  "rules":{}
}

затем работаетwebpack, он выполнит проверку кода, просматривая кучуwarning,errorРазве это не круто~

Вот общие правила ESLint:eslint.cn/docs/rules/

источник данных

Потому что это для практикиReact, на данный момент рассмотрите возможность создания только статической страницы, и теперь все больше и больше крупных коров любят использоватьGithub IssuesЧтобы вести блог, он также может предоставить лучшую функцию комментариев, поэтому я тоже хочу попробовать.Github Issuesв качестве источника данных для блога.

API здесь:developer.github.com/v3/issues/

Я не читал все API, я просто посмотрел, как получитьIssuesсписок. .

https://api.github.com/repos/axuebin/react-blog/issues?creator=axuebin&labels=blog

Через параметры управленияcreatorа такжеlabels, который можно отфильтровать как отображениеIssues. он вернетissueМассив объектов формата. КаждыйissueЕсть много свойств, нам может не понадобиться так много, сначала поймите следующее:

// 为了方便,我把注释写在json中了。。
[{
  "url": ,  // issue 的 url
  "id": ,  // issue id , 是一个随机生成的不重复的数字串 
  "number": ,  // issue number , 根据创建 issue 的顺序从1开始累加
  "title": ,  // issue 的标题
  "labels": [], // issue 的所有 label,它是一个数组
  "created_at": , // 创建 issue 的时间
  "updated_at": , // 最后修改 issue 的时间
  "body": , // issue 的内容
}]

Запрашивать данные асинхронно

Когда метод асинхронного запроса данных, используемый в проектеfetch.

оfetch:сегмент fault.com/ah/119000000…

Это просто в использовании:

fetch(url).then(response => response.json())
      .then(json => console.log(json))
      .catch(e => console.log(e));

рендеринг уценки

существуетGithubУзнайте, какReactвыполнитьmarkdown, нашел эти две библиотеки:

Он очень прост в использовании.

еслиreact-markdown, просто сделайте это:

import ReactMarkdown from 'react-markdown';

const input = '# This is a header\n\nAnd this is a paragraph';
ReactDOM.render(
    <ReactMarkdown source={input} />,
    document.getElementById('container')
);

еслиmarked,Сюда:

import marked from 'marked';

const input = '# This is a header\n\nAnd this is a paragraph';
const output = marked(input);

Здесь немного по-другому, мы получаем строкуoutput, обратите внимание, это строка, поэтому мы должны вставить ее вdomв, вReactМы можем это сделать:

<div dangerouslySetInnerHTML={{ __html: output }} />

Так как наш проект основан наReact, поэтому я подумал об использованииreact-markdownбыло бы лучше, и из соображений безопасностиReactне выступает за прямоеdomВставьте в него строку, но в процессе использования обнаруживается, чтоreact-markdownПоддержка таблиц не является дружественной, поэтому я должен отказаться от нее и использовать вместо нееmarked.

подсветка кода

Подсветка кода используетhighlight.js:GitHub.com/ISA gala EV/ также…

это иmarkedМожет быть легко подключен ~

Просто сделайте это:

import hljs from 'highlight.js';

marked.setOptions({
  highlight: code => hljs.highlightAuto(code).value,
});

highlight.jsОн поддерживает различные стили сопоставления цветов кода, которые можно найти вcssпереключиться в файле:

@import '~highlight.js/styles/atom-one-dark.css';

Здесь вы можете увидеть стили выделения и подбора цветов для каждого языка:highlightjs.org/

React

что такое состояние и реквизит

Смотрите предыдущую статью:GitHub.com/Axue Temple/Hot Ah…

Жизненный цикл React Components

Смотрите предыдущую статью:GitHub.com/Axue Temple/Hot Ah…

Внешняя маршрутизация

Интерфейсная маршрутизация в проектеReact-Router V4.

Официальная документация:реагировать-обучение.com/реагировать-маршрут…

Китайский документ:reacttraining.cn/

основное использование

<Link to="/blog">Blog</Link>
<Router>
  <Route exact path="/" component={Home} />
  <Route path="/blog" component={Blog} />
  <Route path="/demo" component={Demo} />
</Router>

Примечание: должен находиться в корневом каталогеRouteзаявление вexact, иначе вы не сможете перейти по любой ссылке.

Переход в каталог уровня 2

Например, если я хочу сейчас щелкнуть по странице блога,urlдаlocalhost:8080/blog, должен статьlocalhost:8080/blog/article, что можно сделать так:

<Route path={`${this.props.match.url}/article/:number`} component={Article} />

Это перейдет кlocalhost:8080/blog/article, а также прошелnumberпараметр, вarticleсквозьthis.props.params.numberПолучать.

HashRouter

Когда я размещаю проект наGithub PageПотом возникла такая проблема.

Обновите страницу, чтобы появилосьCannot GET /Подскажите, роутинг не действует.

Понимая, я знаю, что причина в этом, и ее можно решить:

  • Поскольку после обновления запрос будет отправлен на сервер на основе URL-адреса вместо обработки маршрута, что приведет кCannot GET /ошибка.
  • путем изменения<Router><HashRouter>.
  • <HashRouter>Маршрутизация достигается с помощью хэша в URL-адресе. Цель переключения страниц может быть достигнута без полноэкранного обновления.

После перехода по маршруту он не будет автоматически возвращаться наверх

После того, как текущая страница будет прокручена до определенной области, после нажатия кнопки «Перейти» страница хоть и перепрыгнет, но останется в области прокрутки и не вернется автоматически к началу страницы.

Это можно решить, выполнив следующие действия:

componentDidMount() {
    this.node.scrollIntoView();
}

render() {
  return (
    <div ref={node => this.node = node} ></div>
  );
}

государственное управление

В проекте необходимо использоватьGithub IssuesЗапрошенные данные, поскольку они были известны ранееReduxХотя наличие этой штуки немного избыточно, чтобы изучить или использовать ее для управления статусом проекта, достаточно один раз запросить данные.

Официальная документация:redux.js.org/

Китайский документ:cn.redux.js.org/

Проще говоря, каждый раз, когда состояние изменяется, его нужно запускать.action, Однако на самом деле измененные данные 2333 я еще не использовал в проекте. . .

Что касается управления состоянием, так как я мало в этом разбираюсь, я не пойму детей неправильно~

основные компоненты

React строится на основе компонентов, поэтому в начале построения страницы мы должны сначала подумать, какие компоненты нам нужны, какова взаимосвязь между этими компонентами, какие компоненты можно использовать повторно и так далее.

титульная страница

Как видите, я в основном разделил домашнюю страницу на четыре части:

  • заголовок: заголовок сайта, подзаголовок, панель навигации
  • баннер: обо мне ~, собираюсь поменять фон на свое фото, но подходящего фото пока нет
  • область карт: пока три карты
    • карточка блога: последние сообщения в блоге
    • демо-карта: несколько небольших демо-категорий
    • мне карта: это место, где я позволяю себе идти
  • нижний колонтитул: информация об авторских правах, информация о регистрации, просмотры страниц

страница блога

Страница блога — очень стандартная страница, это часть с наибольшим объемом кода во всем проекте, включая следующие части:

  • Компонент списка статей
  • Компонент перелистывания страниц
  • Компонент кнопки архивации
  • Компонент категории
  • Компонент этикетки

Список статей

Список статей на самом делеlist, есть по одномуitem:

<div class="archive-list">
  <div class="blog-article-item">文章1</div>
  <div class="blog-article-item">文章2</div>
<div>

для каждогоitem, что на самом деле так:

Компонент элемента статьи может включать:

  • название статьи
  • Время, категория, тег и т. д., когда статья была опубликована.
  • Резюме статьи
  • ...

Если вы используетеDOMЧтобы описать это, это должно выглядеть так:

<div class="blog-article-item">
  <div class="blog-article-item-title">文章标题</div>
  <div class="blog-article-item-time">时间</div>
  <div class="blog-article-item-label">类别</div>
  <div class="blog-article-item-label">标签</div>
  <div class="blog-article-item-desc">摘要</div>
</div>

Итак, у нас может быть много компонентов:

  • Компонент списка статей<ArticleList />
  • Компонент элемента статьи<ArticleItem />
  • Компонент метки категории<ArticleLabel />

Это могут быть такие отношения:

<ArticleList>
  <ArticleItem>
    <ArticleTitle />
    <ArticleTime />
    <ArticleLabel />
    <ArticleDesc />
  </ArticleItem>
  <ArticleItem></ArticleItem>
  <ArticleItem></ArticleItem>
</ArticleList>

нумерация страниц

Для функции пейджинга традиционный метод реализации состоит в том, чтобы завершить пейджинг на бэкенде, а затем вернуть его во внешний интерфейс в пакетах.Например, может быть возвращена часть данных, подобная этой:

{
  total:500,
  page:1,
  data:[]
}

То есть бэкэнд вернет данные разделенных страниц, включая общий объем данных.total, текущее количество страницpage, а данные, принадлежащие страницеdata.

Однако моя страница — это просто статическая страница, и данные извлекаются через API на Github Issues. (Похоже, что разбиение по страницам Github Issues не может настроить число...), поэтому нет способа напрямую вернуть разделенные данные, поэтому вы можете принудительно разбивать страницы только на внешнем интерфейсе ~

Мне лень пользоваться функцией пейджинга... Используюantdкомпонент перелистывания страниц<Pagination />.

Официальная документация:Anta.design/components/…

Документация понятна и очень проста в использовании.

Логика внешнего рендеринга (немного глуповатая): хранить данные в массиве, вычислять значение индекса отображения в соответствии с текущим количеством страниц и количеством баров, отображаемых на каждой странице, и извлекать соответствующие данные.

В компоненте перелистывания страниц:

constructor() {
  super();
  this.onChangePage = this.onChangePage.bind(this);
}

onChangePage(pageNumber) {
  this.props.handlePageChange(pageNumber);
}

render() {
  return (
    <div className="blog-article-paging">
      <Pagination onChange={this.onChangePage} defaultPageSize={this.props.defaultPageSize} total={this.props.total} />
    </div>
  );
}

Когда количество страниц изменится, это вызовет передачу из родительского компонента.<ArticlePaging />МетодыhandlePageChange, тем самым передавая количество страниц в родительский компонент, который затем передается в<ArticleList />середина.

В родительском компоненте:

handlePageChange(pageNumber) {
  this.setState({ currentPage: pageNumber });
}

render() {
  return (
    <div className="archive-list-area">
      <ArticleList issues={this.props.issues} defaultPageSize={this.state.defaultPageSize} pageNumber={this.state.currentPage} />
      <ArticlePaging handlePageChange={this.handlePageChange} total={this.props.issues.length} defaultPageSize={this.state.defaultPageSize} />
    </div>
  );
}

Список:

render() {
  const articlelist = [];
  const issues = this.props.issues;
  const currentPage = this.props.pageNumber;
  const defaultPageSize = this.props.defaultPageSize;
  const start = currentPage === 1 ? 0 : (currentPage - 1) * defaultPageSize;
  const end = start + defaultPageSize < issues.length ? start + defaultPageSize : issues.length;
  for (let i = start; i < end; i += 1) {
    const item = issues[i];
    articlelist.push(<ArticleItem />);
  }
}

label

существуетGithub Issues, может бытьissueдобавить многоlabel, я использую их как полезные для контента блогаlabelОн разделен на три категории, которые представлены разными цветами.

Объясните здесь,labelПосле создания случайныйid,хотяidНе повторяется, но рубрики и теги статьи будут увеличиваться при добавлении новойlabel, программа может также нуждаться в соответствующей модификации, в качестве различияlabelСтандарт может быть неподходящим, поэтому я использую цвет, чтобы различать их.

  • Блог с указанием, что это статья: только если естьblogизissueдля отображения на странице, фильтрbug,helpЖдать
  • Указывает категорию статьи: используется для обозначения категории статьи, например, «внешний интерфейс», «фотография» и т. д.
  • Для тегов статей: теги, используемые для обозначения статей, такие как «JavaScript», «React» и т. д.

даже с новымlabel, если он классифицируется в соответствии с цветом, к какой категории он принадлежит.

категория

Основная идея здесь такова: пройти всеissues, затем перебирает каждыйissueизlabels, Найтиlabel, то посчитай.

const categoryList = [];
const categoryHash = {};
for (let i = 0; i < issues.length; i += 1) {
  const labels = issues[i].labels;
  for (let j = 0; j < labels.length; j += 1) {
    if (labels[j].color === COLOR_LABEL_CATEGORY) {
      const category = labels[j].name;
      if (categoryHash[category] === undefined) {
        categoryHash[category] = true;
        const categoryTemp = { category, sum: 1 };
        categoryList.push(categoryTemp);
      } else {
        for (let k = 0; k < categoryList.length; k += 1) {
          if (categoryList[k].category === category) {
            categoryList[k].sum += 1;
          }
        }
      }
    }
  }
}

Для этого требуется три цикла. Сложность немного высока, и это кажется немного глупым. Его нужно улучшить. Если есть лучший метод, пожалуйста, дайте мне больше советов ~

Этикетка

Идея здесь в основном такая же, как и идея категории, но отображается по-другому.

Изначально я хотел использовать размер шрифта, чтобы отразить вес каждого тега. Позже я подумал, что, может быть, для меня только эти теги будут очень частыми на данный момент, а других тегов может быть очень мало. различать по размеру шрифта или изменить способ сортировки.

Страница статьи

Страница статьи в основном разделена на две части:

  • Область содержания статьи: отображение содержимого статьи, отображаемое в основной области страницы
  • Содержание главы: оглавление главы статьи, отображаемое в правой части статьи.

Содержание статьи

Есть два способа получить конкретное содержание статьи:

  • Перебрать массив, чтобы найти желаемое содержимое статьи из массива, который был запрошен ранее.
  • пройти черезissue numberПовторно отправьте запрос, чтобы получить контент напрямую

Наконец я выбрал последний.

артикль используетсяmarkdownОн написан грамматически, поэтому его нужно преобразовать вhtmlЗатем вставьте его на страницу, здесь мы используемReactУстаревшие атрибуты:dangerouslySetInnerHTML.

За исключением рендерингаmarkdownПришлось код статьи подсвечивать, есть пользовательские стили статей разными метками.

Содержание главы

Во-первых, вотissue, я надеюсь, что вы можете дать некоторые предложения ~

Содержание статьи черезmarkdownВставить после рендерингаdomв, потому чтоReactНе рекомендуетсяdocument.getElementByIdПриобретениеdomэлемент, поэтому мы можем найти способ получить заголовок каждой главы статьи только путем сопоставления строк.

Поскольку я не очень хорошо знаком с регулярными выражениями, я однажды проконсультировался на sf и принял один из ответов:

const issues = content;
const menu = [];
const patt = /(#+)\s+?(.+)/g;
let result = null;
while ((result = patt.exec(issues))) {
  menu.push({ level: result[1].length, title: result[2] });
}

Это получит все#строка, то естьmarkdownназвание в ,result[1].lengthозначает, сколько#, на самом деле это означает несколько уровней заголовков,titleЭто просто название.

Здесь есть еще одна проблема, которая изначально была пропущена через<a target="" />Способ добиться перехода по щелчку, но теперь визуализируетсяhtmlДля каждого заголовка нет уникального идентификатора. . .

Архивная страница

Архив по годам:

Архив по категориям:

Архив по тегу:

вопрос

Основные функции в основном реализованы, и остаются следующие проблемы, которые можно рассматривать как однуTodoListБар

  • Функция комментариев. предназначен для использованияGithub Issues APIРеализовать комментарии, должны реализоватьGithubАвторизованный вход
  • вернуться наверх. предназначен для использованияantdкомпоненты, ноstateсерединаvisibilityвсегдаfalse
  • Рендеринг главной страницы. Теперь упакованный файл js все еще слишком велик, что делает рендеринг домашней страницы слишком медленным.Это основная цель следующей работы, и я узнал об оптимизации в этом отношении:
    • webpackНагрузка по требованию. Это, вероятно, самый удобный способ на данный момент
    • Рендеринг на стороне сервера. Это хлопотно, но есть много преимуществ, не только для решения проблемы рендеринга, но и для SEO, так что это такжеtodoодин
  • Код запутан, а логика неверна. Это моя собственная проблема, и мне нужно снова попрактиковаться.

Оригинальный адрес:GitHub.com/Axue Temple/Hot Ah…