[分享]用react實現簡書項目

寫在開頭:

感覺社會變化很快,我記得從我第一次學習css js jquery的時候,我都一直希望趕緊學習後端,因爲覺得後端工資高,一直忽視前端,到現在才猛然發現,前端要解決的問題太多,前端越來越有競爭力,自己彷彿在不知不覺中被甩遠了,後來從java轉到了ruby後端,才意識到全棧纔是自己真正想要達到的一個階段,同事在離職前給我一個英文版的項目視頻,因爲現在的英文能力有限,所以購買了箇中文版的項目,想利用休息時間,好好能系統過一遍,也想着記下筆記,利於自己複習,也方便他人。最後有地址,有完成之後的項目,是用react實現的簡書項目

一:react開發環境準備

1.安裝node

node官網下載地址

LTS比較穩定的版本 current最新的版本
安裝完成後
node -v npm -v
輸出成功,說明node就安裝成功了。

2.創建react腳手架

react官網-create-app

npm install -g create-react-app

注意:如果出現Error: EACCES: permission denied, access '代表沒有權限,加上sudo 命令即可。

create-react-app my-app
create-react-app helloWorld

Could not create a project called "helloWorld" because of npm naming restrictions:
* name can no longer contain capital letters

這裏會自動幫你去構建react的項目工程。比如去安裝一些react所需要的依賴包。

Success! Created helloworld at /Users/sai/Desktop/helloworld
Inside that directory, you can run several commands:

  yarn start
    Starts the development server.

  yarn build
    Bundles the app into static files for production.

  yarn test
    Starts the test runner.

  yarn eject
    Removes this tool and copies build dependencies, configuration files
    and scripts into the app directory. If you do this, you can’t go back!

We suggest that you begin by typing:

  cd helloworld
  yarn start
cd helloworld
npm run start

腳手架工具幫我們分析源代碼,啓動一個服務器把源代碼運行起來,看到的頁面就是代碼工程生成的頁面

二:工程目錄簡介

通過create-app腳手架生成的主要目錄介紹

  • node_modules:是我們項目所依賴的第三方的包
  • public
    • favicon.ico:圖標
    • index.html:當前顯示的頁面
  • yarn.lock:項目的依賴包、版本號 不要動
    • readme.md:說明文件
    • package.json: node包文件,可以把你的項目變成一個包
// package.json
{
  "name": "helloworld", #名字
  "version": "0.1.0", #版本
  "private": true, #私有
  "dependencies": { #依賴的包
    "react": "^16.4.2",
    "react-dom": "^16.4.2",
    "react-scripts": "1.1.5"
  },
  "scripts": { #指令
    "start": "react-scripts start", #之所以在命令行中可以輸入npm run start啓動,是因爲在這裏配置了 它去通過react-scripts工具去啓動服務器
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  }
}

gitignore:用過用git進行管理,你不想傳遞到git倉庫,可以放到這裏。

  • src
    • index.js:
// index.js
import React from 'react';  
import ReactDOM from 'react-dom';

import.. from是es6提供的模塊引入的語法。
這裏所引入的React ReactDOM對應的package.jsondependencies存在的庫。

// PWA progressive web application
// 通過寫網頁的形式來寫手機app應用
// 寫一個網頁,上線到https服務器上,當用戶斷網的時候,再訪問的時候還是能看到之前已經加載好的頁面
import registerServiceWorker from './registerServiceWorker';

三:react中的組件

一個頁面如果很複雜,我們寫起來很麻煩。但是如果寫成一個個可拆分的組件的時候,我們就好管理的多。而且組件的形式也可重用性強。這也就是前端同學經常說的 前端組件化 的含義。

這裏再針對於本章說下最基礎的jsx語法

  • 我們在之前的js返回一般寫的是return <div>hello, jianshu</div>,而在jsx中是不加但雙引號的。另外,在jsx中,不單單可以引入普通的標籤,還可以引入組件。
  • 在jsx引入組件的時候,比如引入<App />組件,這裏的組件首字母必須要大寫,不能是<app />,因爲這種jsx語法是不支持的,所以要用jsx語法,首字母必須大寫。所以,大寫字母開頭的話,一般是組件。

從腳手架生成的app.js我們進行分析

// app.js
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';

class App extends Component {   // 用es6的語法寫了繼承
  render() {
    return (
      <div>
         hello, jianshu.
      </div>
    );
  }
}

export default App; //導出操作,正好index.js引入了,所以顯示出來了

app.jsrender下的<div>也是jsx語法,所以必須引入React,引入之後,才能編譯成功。

// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';

ReactDOM.render(<App />, document.getElementById('root'));
registerServiceWorker();

ReactDOM是第三方的模塊,它有一個方法是render,這個方法幫我們把一個組件掛在dom節點上。這裏掛在在root下。

<div id="root">
...
</div>

<App />jsx語法,需要引入對應的react。

再整體介紹一遍:

index.js是程序入口,引入React ReactDOM,,接着引入了一個組件,叫APP,用reactdom把這個組件顯示在root節點。
因爲這裏用了jsx語法,所以需要引入import React, { Component } from 'react';,然後再看app.js,它就是個名字叫App的組件,在這個組件中返回了一些內容,因爲它是一個react組件,所以必須引入import React, { Component } from 'react';,這個App必須繼承這個React.Component纔可以生成這個組件。

1.1 初探

index.js:

import React from 'react';
import ReactDOM from 'react-dom';
import TodoList from './TodoList';

ReactDOM.render(<TodoList />, document.getElementById('root'));

todoList.js

import React, { Component } from 'react';
import ReactDOM from 'react-dom';

class TodoList extends Component {
  render() {
    return (
      <div>TodoList</div>
      <ul>
        <li>學習</li>
        <li>寫作</li>
      </ul>
    )
  }
}

export default TodoList; //把自身導出,外部纔可以引用。

會報下面的錯
./src/TodoList.jsSyntax error: Adjacent JSX elements must be wrapped in an enclosing tag (8:6)
這是因爲在jsx的語法中,一個組件render函數返回的內容外層必須要有一個包裹元素。現在有兩個,一個<ul>,一個 <div>,在外層加個<div>即可。
如果不想套用一個div怎麼辦?
在react16的語法中,react提供了一個叫Fragiment的佔位符。

return (
      <Fragment>
        <div>TodoList</div>
        <ul>
          <li>學習</li>
          <li>寫作</li>
        </ul>
      </Fragment>
    )

2.2 React中的響應式設計思想和事件綁定

react英文就是響應的意思,react跟英文名一樣的含義:數據響應,頁面發生變化。
react更改state的狀態的話不能通過以下來進行修改

this.state.inputValue = e.target.value //e.target會獲取dom節點,再.value就會獲取value值

而是用react提供的方法setState

import React, { Component, Fragment } from 'react';
import ReactDOM from 'react-dom';

class TodoList extends Component {
  constructor(props) {
    super(props)
    this.state = {
      inputValue: '',
      list: []
    }
  }

  handleInputChange (e) {
    this.setState({
      inputValue: e.target.value
    })
  }

  render() {
    return (
      <Fragment>
        <div>
          <input value={this.state.inputValue} onChange={this.handleInputChange.bind(this)} />
          <button>提交</button>
        </div>
        <ul>
          <li>學習</li>
          <li>寫作</li>
        </ul>
      </Fragment>
    )
  }
}

export default TodoList; //把自身導出,外部纔可以引用。

介紹:
頁面剛開始初始化的時候,會有兩個初始值inputValuelist值,render會渲染值爲this.state.inputValue會初始化值,然後輸入內容onChange會執行,e.target.value會獲取值,然後改變inputValue的值,inputValue的值發生改變了,頁面input標籤裏的值發生了改變,那麼頁面也會發生改變。

注意:
js表達式用{}
時間綁定用bind(this)對函數的作用域進行變更,改變this的指向
setState來對數據項就行改變

2.3 實現todoList新增刪除功能

增加功能

list: ['study1', 'study2']
...
<ul>
  {
    this.state.list.map((item, index) => {
      return <li>{item}</li>
    })
  }
</ul>

這裏就會有生成兩個li,也就是list中的值。所以我們實現增加功能,不用關注dom,只需要關注數據就行了。要做的就是把inputValue的值放在 list中即可。這樣數組中有內容,頁面就會接着變了。

handleBtnClick() {
    this.setState({
      list: [...this.state.list, this.state.inputValue]
    })
}

...是es6的展開運算符,...this.state.list指的是初始化是時候list所定義的值。把以前的數組展開生成全新的數組,所以外面套個[]


會發現頁面報Warning: Each child in an array or iterator should have a unique "key" prop.

<ul>
  {
    this.state.list.map((item, index) => {
      return <li key={index}>{item}</li>
    })
  }
</ul>

map循環需要加上key,key必須保證唯一,要不會報警告,這裏用index

刪除功能

點擊item的時候獲取下標值。把index放在bind函數中,傳遞給delete方法,用splice方法刪除即可。

import React, { Component, Fragment } from 'react';
import ReactDOM from 'react-dom';

class TodoList extends Component {
  constructor(props) {
    super(props)
    this.state = {
      inputValue: '',
      list: []
    }
  }

  handleInputChange (e) {
    this.setState({
      inputValue: e.target.value
    })
  }

  handleBtnClick() {
    this.setState({
      list: [...this.state.list, this.state.inputValue],
      inputValue: ''
    })
  }

 // 刪除
  handleItemDelete (index) {
    // immutable
    // state 不允許我們做任何改變
    const list = [...this.state.list]
    list.splice(index, 1)

    this.setState({
      list: list
    })
  }

  render() {
    return (
      <Fragment>
        <div>
          <input value={this.state.inputValue} onChange={this.handleInputChange.bind(this)} />
          <button onClick={this.handleBtnClick.bind(this)}>提交</button>
        </div>
        <ul>
          {
            this.state.list.map((item, index) => {
              return (<li
                        key={index}
                        onClick={this.handleItemDelete.bind(this, index)}>
                        {item}</li>
                      )
            })
          }
        </ul>
      </Fragment>
    )
  }
}

export default TodoList; //把自身導出,外部纔可以引用。

一:jsx語法細節補充

  • 定義css的類的時候,不能用class,要用className來代替class這個關鍵詞,因爲我們用class已經聲明類了,這樣會重複定義。
  • 註釋的話用{/*我是註釋*/}
  • 點擊label獲取input的光標,可以在input上定義一個id,比如這個id是insertArea,那麼在label中加htmlFor。代替之前的for 關鍵字。
  • import React, { Component },這裏的{ Component}是結構賦值。

二:拆分組件與組件之間的傳值

組件化思維

react是樹形的結構,這裏todoList是大組件,todoItem是todoList下的小組件。

父組件向字組件傳遞數據用屬性才進行傳遞,通過標籤屬性傳遞(屬性和方法)過去之後,子組件用this.props.*來進行接收。
子組件如何調用父組件的方法,並改變裏面的數據?把父組件的方法傳給子組件即可。用this.props.*,父組件如果傳遞方法,需要做一次綁定。
要不然會出現下面的錯誤:
TypeError: Cannot read property 'list' of undefined

三:優化todoList

// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import TodoList from './TodoList';

ReactDOM.render(<TodoList />, document.getElementById('root'));

//TodoList.js
import React, { Component, Fragment } from 'react';
import TodoItem from './TodoItem';

class TodoList extends Component {
  constructor(props) {
    super(props)
    this.state = {
      inputValue: '',
      list: []
    }
    this.handleInputChange = this.handleInputChange.bind(this);
    this.handleBtnClick = this.handleBtnClick.bind(this);
    this.handleItemDelete = this.handleItemDelete.bind(this);
  }

  handleInputChange (e) {
    const value = e.target.value
    this.setState(() => ({
      inputValue: value
    }))
    // old
    // this.setState({
    //   inputValue: e.target.value
    // })
  }

  handleBtnClick() {
    //setState有一個參數可以接收以前的數據
    this.setState((prevState) => ({
      list: [...prevState.list, prevState.inputValue],
      inputValue: ''
    }))

    // old
    // this.setState({
    //   list: [...this.state.list, this.state.inputValue],
    //   inputValue: ''
    // })
  }

  handleItemDelete (index) {
    // immutable
    // state 不允許我們做任何改變
    this.setState((prevState) => {
      const list = [...prevState.list]
      list.splice(index, 1);
      return {list}
    });
  }

  getTodoItem () {
    return this.state.list.map((item, index) => {
      return (
        <TodoItem
          key={index}
          content={item}
          index={index}
          deleteItem={this.handleItemDelete}
        />
      )
    })
  }

  render() {
    return (
      <Fragment>
        <div>
          <input value={this.state.inputValue} onChange={this.handleInputChange} />
          <button onClick={this.handleBtnClick}>提交</button>
        </div>
        <ul>
          {this.getTodoItem()}
        </ul>
      </Fragment>
    )
  }
}

export default TodoList; //把自身導出,外部纔可以引用。
//TodoItem.js
import React, { Component } from 'react';

class TodoItem extends Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    const { deleteItem ,index } = this.props
    deleteItem(index);
  }

  render() {
    const { content } = this.props
    return (
      <div onClick={this.handleClick}>
        {content}
      </div>
    )
  }
}

export default TodoItem;

四:圍繞react衍生出的思考

  • 直接操作dom的開發方式是命令式開發的方式,比如我們熟悉的jquery
  • 而react是聲明式開發 可以與其他框架並存 組件式開發
  • react是單向數據流,只允許父組件向子組件傳遞數據,子組件絕對不能修改父組件傳遞的數據,而必須要刪除的話是子組件調用父組件的方法,然後進行刪除,這裏實際上也是調用對父組件進行操作,這樣只需要維護父組件即可,維護起來比較容易
  • react是一個視圖層框架,小型項目即可。而大型的需要依賴Flux redux等這樣的數據層框架
  • 函數式編程,都是一個一個的函數組成,方便於測試,給前端的自動化測試帶來很大的便捷性。

react實現簡書項目

簡書項目地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章