ReactでMarkdown Editorを爆速で作る

n番煎じだけど気にしない。完成品。

f:id:fuji_haruka:20170701002701p:plain

1. create-react-appでひな型を作る

create-react-appを使う。

$ yarn global add create-react-app
$ create-react-app react-markdown-editor

待つこと数十秒。yarn が速いので助かる。

2. Github Pages にデプロイする

create-react-appのドキュメントによると、Github Pages にデプロイするには、gh-pagesを使うといいらしい。

Github にレポジトリを作ってから、

$ yarn add gh-pages

して、package.jsonにデプロイ用のスクリプトを書き加える。

  "scripts": {
    "predeploy": "npm run build",
    "deploy": "gh-pages -d build",
    ...

試しに yarn deploy をしてみると、きちんと Github Pages にデプロイされていた。

作る。

App.js を書き換える。まずはテキストエリアと、テキスト表示を用意する。テキストエリアの値が更新されるたびに state に入れて、テキスト表示場所を更新する。

import React, { Component } from 'react'
import autoBind from 'react-autobind'
import './App.css'

class App extends Component {
  constructor (props) {
    super(props)
    autoBind(this)
    this.state = {
      text: '',
      lines: []
    }
  }

  render () {
    let {
      text,
      lines
    } = this.state
    return (
      <div className='App'>
        <div className='App-header'>
          Markdown Editor
        </div>
        <div className='App-body'>
          <textarea
            className='App-box App-textarea'
            value={text}
            onChange={this.onChangeText}
          />
          <div className='App-box App-textview'>
            {
              lines.map((line, i) =>
                <div className='App-line' key={i}>{line}</div>
              )
            }
          </div>
        </div>
      </div>
    )
  }

  onChangeText (e) {
    let text = e.target.value
    let lines = text.split('\n')
    this.setState({
      text: e.target.value,
      lines: lines
    })
  }
}

export default App

とりあえずこれで、(マークダウンではない)テキストエディタができた。

次に、marked を使って、Markdown 文字列を HTML 文字列に変換する。React で表示する際には、dangerouslySetInnerHTMLを使う。

import React, { Component } from 'react'
import autoBind from 'react-autobind'
import marked from 'marked'
import './App.css'

class App extends Component {
  constructor (props) {
    super(props)
    autoBind(this)
    this.state = {
      text: '',
      markedLines: []
    }
  }

  render () {
    let {
      text,
      markedLines
    } = this.state
    return (
      <div className='App'>
        <div className='App-header'>
          Markdown Editor
        </div>
        <div className='App-body'>
          <textarea
            className='App-box App-textarea'
            value={text}
            onChange={this.onChangeText}
          />
          <div className='App-box App-textview'>
            {
              markedLines.map((line, i) =>
                <div
                  key={i}
                  className='App-line'
                  dangerouslySetInnerHTML={line}
                   />
              )
            }
          </div>
        </div>
      </div>
    )
  }

  onChangeText (e) {
    let text = e.target.value
    let markedLines = text
      .split('\n')
      .map((line) => ({ __html: marked(line) }))
    this.setState({
      text,
      markedLines
    })
  }
}

export default App

あとはCSSを書いて完成!所要時間は約1時間。

課題

まあ、これはまだ不完全で、一行ずつ HTML に変換しているため、たとえば

1. foo
2. bar
3. baz

なんかがうまく表示されない。

あとは、1文字表示されるたびにテキスト全体を marked() しているのも効率悪い。