碼雲Gitee 源碼連接css
若是你想學React,可是看了半天沒有好一點的上手demo,請看這裏 =>html
想學個React,滿大街的todo-list,讓我難免有點心累。前端
想到我去年同期自學Vue的時候,還有個小增刪改查的小例子。因而靈機一動,掏出了去年的那個小教程(Vue + Vue Router)。vue
爲什麼不復刻一個React版呢?node
這裏貼一下本文靈感來源,感謝這篇文章把我帶上前端的道路 Vue版人員管理系統 連接 。react
因爲其餘緣由,Vue版的圖片就不貼了。自行點開上面的鏈接看一看吧webpack
先上咱們React版的動圖git
主要有如下功能:web
OK,那咱們從建立項目開始,一步一步來算法
運行環境:
首先,咱們的Node.js環境不可少,沒有的請去nodejs官網安裝一下。推薦LTS版最好
安裝完畢Node.js後,打開cmd命令行窗口。輸入 node -v
和 npm -v
若是以下圖所示,說明安裝成功
接着咱們開始配置React開發環境:
在這裏,咱們使用Facebook 官方推薦的React腳手架 - create-react-app。
這個腳手架已經作好了基礎 webpack 配置,帶有自動更新,錯誤提示等等功能,僅僅須要建立,啓動就能夠快速開發。
控制檯輸入 npm install -g create-react-app
若是網絡環境很差,請用淘寶的cnpm進行安裝
建立一個React項目:
create-react-app manager複製代碼
其中,create-react-app 是命令名。manager
是項目名
若是控制檯提示 Happy hacking
說明環境安裝完畢
這時候,咱們能夠用IDE打開這個項目文件夾啦!
首先咱們先看一下目錄:
下面咱們來看看package.json 裏的內容
重點關注 scripts 字段內的內容
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},複製代碼
「start」 這個命令,就是在你的本地啓動一個dev服務器。而後能夠預覽代碼結果
「build」 這個命令,會把源碼文件編譯打包後輸出,方便發佈到服務器。
「test」 這個命令,會對源碼進行斷言測試。
「eject」 這個命令,有點像「彈出」的意思。你如今看到的配置,都是簡化後的配置。若是想自定義配置(如Webpack),請執行這個命令。不過值得注意的是:此命令執行後,操做沒法回退
咱們如今執行 yarn run eject
這個命令
在這裏先普及一下yarn 這個工具
Yarn與 npm 同樣,是一款 NodeJS 包管理工具。 爲什麼要選擇使用 yarn 呢?官網的描述是:
Yarn 會緩存它下載的每一個包,因此不須要重複下載。它還能並行化操做以最大化資源利用率,因此安裝速度之快史無前例。
Yarn 在每一個安裝包的代碼執行前使用校驗碼驗證包的完整性。
Yarn 使用一個格式詳盡但簡潔的 lockfile 和一個精確的算法來安裝,可以保證在一個系統上的運行的安裝過程也會以一樣的方式運行在其餘系統上。
選用 Yarn 的緣由也是由於他的速度提高比npm 要快,使用yarn add <package-name>
,yarn remove <package-name>
增刪 node 包(對應npm install
和npm uninstall
).
開始後他會提示你 「此操做不可逆,請謹慎操做」。想好了請輸入Y
若是錯誤列出了部分文件,請關閉IDE,刪除掉目錄下的.git 目錄和ide建立的文件。用CMD執行一遍
執行完畢後,咱們會發現 script 命令變成了:
"scripts": {
"start": "node scripts/start.js",
"build": "node scripts/build.js",
"test": "node scripts/test.js"
},複製代碼
這下都是node原生命令了,目錄也多了個config文件夾,config文件夾內主要是一些webpack的配置。在這裏很少解釋
接下來咱們嘗試跑一下。命令行輸入:yarn run start
而後會拋出一個錯誤:
Cannot find module '@babel/plugin-transform-react-jsx'
這個問題是由於eject過程當中的出錯。
解決方案是刪除掉node_modules文件夾,用 npm install
從新安裝一遍。在這裏我用的是cnpm
若是咱們看到下圖,說明咱們的開發環境搭建完畢。
首先,咱們項目中不須要單元測試和PWA。能夠把 src
App.test.js
和serviceWorker.js
刪掉(自行了解PWA)
index.js 改造後
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));複製代碼
咱們引入React,ReactDom是React的基礎組件,App是表明根組件。
render方法的第一參數接收一個根組件,第二參數接受一個須要掛載的html節點。組件編譯後的內容就會加載在html節點內,其餘的什麼都不須要引入。
咱們在src下新建兩個文件夾,名稱分別是「index」和「manager」。分別表明咱們的兩個路由
裏面也分別新建兩個同名的js和css文件。分別表明兩個路由組件和CSS。就是這樣子的
接着把index.css也刪除掉,只留下App.css一個作全局公共樣式就好
app.css 內暫時寫如下內容:
* {
margin: 0 auto;
padding: 0;
}
a {
text-decoration: none !important;
}複製代碼
去除一下網頁邊框的 margin 和 padding。全部a標籤去除下劃線
App.js也很簡單。只留下一個React組件模板
import React, { Component } from 'react';
import './App.css';
class App extends Component {
render() {
return (
<div className="App">
</div>
);
}
}
export default App;
複製代碼
咱們在App.js 內,把咱們的React logo引入進來。由於React的logo是兩個路由公共的東西
import logo from "./logo.svg";複製代碼
下面的也改爲這個佈局:
class App extends Component {
render() {
return (
<div className="App">
<img src={logo} alt=""/>
<div className="tab-bar">
<a className="tab-bar__active">首頁</a>
<a>人員管理</a>
</div>
</div>
);
}
}複製代碼
上面一個React的Logo,而後下面是一個底部導航欄
修改一下app.css
* {
margin: 0 auto;
padding: 0;
}
a {
text-decoration: none !important;
}
.App {
text-align: center;
}
.App img {
width: 300px;
}
.tab-bar {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
position: fixed;
bottom: 0;
width: 100%;
}
.tab-bar a {
flex: 1;
padding: 16px;
background-color: #F1F1F1;
color: #000;
text-decoration: none;
}
.tab-bar__active {
background-color: #737171 !important;
color: #61DAFB !important;
}
複製代碼
接着能夠看到咱們修改後的樣式佈局
但這時候咱們尚未用路由。路由打算最後的時候再使用
首頁組件很簡單,其實只有一行字。但咱們也要去寫,要否則無法展示出路由的用處
index.css裏面是空的
index.js
import React, {Component} from 'react';
export default class Index extends Component {
render() {
return (
<div className="index">
<span>歡迎來到人員管理系統React版</span>
</div>
)
}
}複製代碼
接着咱們把封裝的index組件,引入到App.js內看看
import Index from './index/index.jsx'
複製代碼
// App.js
class App extends Component {
render() {
return (
<div className="App">
<img src={logo} alt=""/>
<Index/>
<div className="tab-bar">
<a className="tab-bar__active">首頁</a>
<a>人員管理</a>
</div>
</div>
);
}
}複製代碼
封裝的Index組件就會實時更新在頁面上:
接下來~~~咱們的重頭戲就來了:管理模塊的編寫。整個demo的核心都在這裏
咱們先寫查詢功能
一樣,咱們先上來也是搭佈局框架
咱們把剛纔引入在App.js的Index組件撤掉。換成Manage組件
// import Index from './index/index.jsx'
import Mannage from './manager/manager.jsx'複製代碼
App.js 內也要修改
// <Index/>
<Mannage/>複製代碼
初始化一下manage.jsx和 manage.css
manage.jsx:
import React, {Component} from 'react';
import './manager.css'
export default class Manager extends Component {
render() {
return (
<div className="manager">
<a className="manager__add">
<span>新增</span>
</a>
<div className="manager__tab">
<div className="manager__tab__header">
<span>姓名</span>
<span>操做</span>
</div>
<div className="manager__tab__list">
<div className="manager__tab__list__item">
<span className="manager__tab__list__item__name">
Janlay
</span>
<span className="manager__tab__list__item__tools">
<span className="item__tools__delete">刪除</span>
</span>
</div>
</div>
</div>
</div>
)
}
}
複製代碼
manage.css:
.manager__add {
text-decoration: none;
padding: 9px 100px;
color: white;
background-color: #61DAFB;
border-radius: 6px;
cursor: pointer;
transition: all .3s;
}
.manager__add span {
user-select: none;
}
.manager__add:focus, .manager__add:active {
background-color: #19c5f4;
}
.manager__input input {
padding: 5px 0;
}
.manager__input a {
padding: 5px 10px;
margin-left: 10px;
color: white;
background-color: #61DAFB;
border-radius: 3px;
}
.manager__tab {
margin-top: 15px;
padding: 0 70px;
}
.manager__tab__header,
.manager__tab__list__item {
display: flex;
}
.manager__tab__header span,
.manager__tab__list__item span {
margin: 0;
flex: 1;
}
.manager__tab__header span:first-child,
.manager__tab__list__item span:first-child {
text-align: left;
}
.manager__tab__header span:last-child,
.manager__tab__list__item span:last-child {
text-align: right;
}
.manager__tab__list {
margin-top: 10px;
overflow: scroll;
height: 300px;
}
.manager__tab__list__item {
margin-top: 5px;
}
.item__tools__delete {
margin-left: 5px !important;
}
複製代碼
初始化後預覽:
在React的內部組件狀態中,使用states進行狀態管理。他和Vue的data 性質是同樣的。但寫法略微有點不一樣:
咱們在構造函數 constructor 內初始化 states 變量。構造函數接受組件外部props變量
constructor(props) {
super(props);
this.state = {
list: [],
};
}複製代碼
這時候就有疑問了,這個super方法是幹什麼的?
是由於當前我們的這個React組件是繼承於官方組件模板。可是繼承後的組件實際上是沒有this對象的。若是想使用this對象,就必需要調用一下super() 方法。不調用的話,就得不到this對象(ES6)
固然,super()不傳參數也沒問題。可是組件內部就沒法使用 this.props 這個對象。因此爲了之後不會忘,就寫上吧
接着賦值 this.state。初始化一遍咱們要用的key值。在這裏,人員的列表變量叫list
這時候,咱們就能夠開始準備渲染了。
React的條件渲染和循環遍歷和Vue的寫法大不同。React的寫法比較偏JS原生寫法,可是定製化高
咱們在render函數內新建一個變量。這個變量存儲咱們的HTML模板:
let list = [];
複製代碼
列表渲染嘛,也要作好非空判斷:
if (this.state.list.length > 0) {
this.state.list.map((data, index) => {
list.push(
<div className="manager__tab__list__item" key={index}>
<span className="manager__tab__list__item__name">
{data.name}
</span>
<span className="manager__tab__list__item__tools">
<span className="item__tools__delete">刪除</span>
</span>
</div>
)
});
} else {
list =
<span>暫無數據</span>
}複製代碼
若是 states 內的列表長度大於0,說明列表內就有數據。這時候遍歷一下列表數據。
若是小於0,就顯示「暫無數據」
若是你連{index} 都看不懂,請回去看一下React基礎語法
這時候不出意料,列表渲染的是「暫無數據」。由於我們初始化就是個空數組
這時候能夠加一條數據看看:
constructor(props) {
super(props);
this.state = {
list: [
{
name: "Janlay"
}
],
};
}複製代碼
結果列表渲染出來了:
查詢功能完成後的Manager.js:
import React, {Component} from 'react';
import './manager.css'
export default class Manager extends Component {
constructor(props) {
super(props);
this.state = {
list: [
{
name: "Janlay"
}
],
};
}
render() {
let list = [];
if (this.state.list.length > 0) {
this.state.list.map((data, index) => {
list.push(
<div className="manager__tab__list__item" key={index}>
<span className="manager__tab__list__item__name">
{data.name}
</span>
<span className="manager__tab__list__item__tools">
<span className="item__tools__delete">刪除</span>
</span>
</div>
)
});
} else {
list =
<span>暫無數據</span>
}
return (
<div className="manager">
<a className="manager__add">
<span>新增</span>
</a>
<div className="manager__tab">
<div className="manager__tab__header">
<span>姓名</span>
<span>操做</span>
</div>
<div className="manager__tab__list">
{list}
</div>
</div>
</div>
)
}
}
複製代碼
咱們的添加按鈕是與input輸入框在同一個位置,因此要用到條件判斷:
在states內新增一個變量,用於切換按鈕和輸入框的顯示隱藏。把它設置爲true
this.state = {
list: [
{
name: "Janlay"
}
],
isShowNew: true
};複製代碼
在render函數內增長
let isShowNew;
if (this.state.isShowNew) {
isShowNew =
<a className="manager__add">
<span>新增</span>
</a>
} else {
isShowNew =
<div className="manager__input">
<input type="text" />
<a>添加</a>
</div>
}複製代碼
而後在return內加入它:
return (
<div className="manager">
{isShowNew}
<div className="manager__tab">
<div className="manager__tab__header">
<span>姓名</span>
<span>操做</span>
</div>
<div className="manager__tab__list">
{list}
</div>
</div>
</div>
)複製代碼
這樣咱們的切換按鈕是否顯示的功能ok了,接下來就該上點擊事件進行切換了
咱們在組件內新增一個方法。用戶隱藏 「添加」 按鈕
showAddInput() {
this.setState({
isShowNew: false
})
}複製代碼
React若是要更改states內的變量,須要用 this.setState()
這個方法。而後裏面包含要更改的key和對應的value
而後咱們在添加按鈕上加入這個方法
<a className="manager__add" onClick={() => this.showAddInput()}>
<span>新增</span>
</a>複製代碼
可能學過react的小夥伴就迷惑了,通常咱們註冊點擊事件的操做是在構造函數內bind一遍啊,爲什麼如今這種方法不用bind就能夠了?
首先了解下平時咱們寫bind()方法和箭頭函數的幾種形式
// 第一種
xxFunction(){..}
...
<XXView xxxx={this.xxFunction.bind(this)} />
複製代碼
// 第二種
constructor(props) {
super(props);
this.xxFunction= this.xxFunction.bind(this);
}
...
xxFunction(){..}
複製代碼
//第三種
xxFunction= ()=>{..};
...
<XXView xxxx={this.xxFunction} />
複製代碼
//第三種
...
xxFunction(){..}
...
<XXView xxxx={()=>this.xxFunction()} />複製代碼
bind函數和箭頭函數區別:
在ES5下,React.createClass會把全部的方法都bind一遍,這樣能夠提交到任意的地方做爲回調函數,而this不會變化。 在ES6下,你須要經過bind來綁定this引用,或者使用箭頭函數(它會綁定當前scope的this引用)來調用。
由此可以得出=>>>bind()方法和箭頭函數在使用是等價的。且在其形式上2和3是一致的、1和4是一致的。
經過 bind() 函數會建立一個新函數(稱爲綁定函數),該新函數是由指定的this值和初始化參數改造的原函數拷貝。 ---> 這裏引用來自CSDN的@學術袁 的文章
那咱們爲什麼要這樣寫呢?
由於咱們的項目中,多個地方調用了 this.setState()。致使頁面渲染時就會執行咱們綁定在html上面的方法。可是setState()方法又會觸發從新渲染,就形成了死循環的現象。因此咱們改成使用箭頭函數的方式進行調用,這樣能夠避免從新渲染,在咱們須要用的時候進行渲染。
接下來,咱們綁定input標籤的onChange事件,監聽輸入框變化。
一樣,先在states內給input一個坑:
this.state = {
isShowNew: true,
list: [],
input: "" //接受input輸入值
};複製代碼
咱們上面講到,使用bind方法其實和使用箭頭函數的方法一致。因此咱們input的監聽事件這樣寫:
inputChange = (event) => {
this.setState(
{
input: event.target.value
}
)
}複製代碼
接收一個event的參數,而後獲取裏面的value值。input添加監聽事件
<input type="text" onChange={this.inputChange}/>複製代碼
有了input監聽事件,咱們也要加上添加事件纔算完整。添加一個add方法
add() {
let list = this.state.list; //獲取當前list的值
list.push({ //添加一條數據
name: this.state.input
});
this.setState({ //從新賦一下值
list: list
})
}
複製代碼
咱們在添加按鈕上添加點擊事件:
<a onClick={() => this.add()}>添加</a>複製代碼
這樣咱們的添加事件就大功告成了!
刪除功能相比增長功能要簡單的多。可是要重寫一下數組的delete方法
增長一個刪除事件,按照數組的下標去刪除
delete(deleteIndex) {
Array.prototype.delete = function (deleteIndex = 0) {
let temArray = [];
for (let i = 0; i < this.length; i++) {
if (i !== deleteIndex) {
temArray.push(this[i]);
}
}
return temArray;
};
let list = this.state.list.delete(deleteIndex);
// delete list[deleteIndex];
this.setState({
list: list
})
}複製代碼
首先咱們擴展了下Array的delete事件,默認刪除下標0的元素。遍歷數組,若是不是要刪除的元素下標,就把數據push進新數組內。最後返回新數組
接着調用刪除方法,而後賦值給state內的list
咱們監聽一下刪除按鈕的Click事件:
<span className="item__tools__delete" onClick={() => this.delete(index)}>刪除</span>複製代碼
最後看一下效果圖:
這樣咱們全部的功能就大功告成啦!
其實這個demo的核心都在路由這裏。
首先要使用react-router,須要先安裝react-router和react-router-dom才能進行使用
在項目目錄下執行:
npm install react-router react-router-dom --save
若是網絡環境很差,請使用 cnpm
首先咱們先選擇路由容器。react有兩種:HashRouter 和 BrowserRouter。
若是使用 HashRouter,你的url會有個#,是由於它經過hash值進行路由控制的。可是不少狀況咱們不用他。咱們用更加優雅一點的 BrowserRouter。它經過HTML5 history API 更改url值。
BrowserRouter 能夠指定一個根url。例如:
<BrowserRouter basename="/"></BrowserRouter>複製代碼
但咱們這裏根url就是「/」。因此就再也不次指定了
接着咱們肯定了路由容器,咱們新增兩個路由視圖,分別表明兩個頁面。
<Route path="/" component={Index}/>
<Route path="/manager" component={Manager}/>複製代碼
path是表明我要指定的路由地址。component 表明這個路由地址掛在哪一個組件上去
最後咱們肯定路由點擊連接,用於路由的切換
在React中,路由連接有兩種:NavLink 和 Link。
Link的api比較簡單,主要是to(要連接的路由地址)。NavLink的功能更多,不只能夠設置to,還能夠設置選中類名和樣式。
在這裏,咱們使用 NavLink。並設置路由選中的樣式類名
<div className="tab-bar">
<NavLink exact to="/" activeClassName="tab-bar__active">
首頁
</NavLink>
<NavLink to="/manager" activeClassName="tab-bar__active">
人員管理
</NavLink>
</div>複製代碼
App.js 修改後:
import React, {Component} from 'react';
import './App.css';
import logo from "./logo.svg";
import {BrowserRouter, NavLink, Route} from 'react-router-dom';
import Index from './index/index.jsx'
import Manager from './manager/manager.jsx'
class App extends Component {
render() {
return (
<BrowserRouter> //路由容器
<div className="App">
<img src={logo} alt=""/>
<Route exact path="/" component={Index}/> //路由視圖
<Route path="/manager" component={Manager}/>
<div className="tab-bar">
<NavLink to="/" activeClassName="tab-bar__active"> //路由連接
首頁
</NavLink>
<NavLink to="/manager" activeClassName="tab-bar__active">
人員管理
</NavLink>
</div>
</div>
</BrowserRouter>
);
}
}
export default App;
複製代碼
咱們打開瀏覽器後,路由可使用。可是驚奇的事情發生了
咱們切換到manage 路由時,發現兩個路由連接都選中了。
這是由於咱們的首頁路由(/)沒有開啓嚴格匹配。若是咱們要跳轉到「/」,React其實會繼續向下匹配,因此他也把「/manage」也一塊兒匹配到了。
解決方案是,在Index的路由視圖和路由連接上上,增長 exact
關鍵字。表明這個路由要嚴格匹配
<Route exact path="/" component={Index}/>複製代碼
<NavLink exact to="/" activeClassName="tab-bar__active">
首頁
</NavLink>複製代碼
這樣咱們再次嘗試一下,會發現問題解決了:
這樣到如今,咱們的這個小demo就大功告成了
做者我做爲一個全職寫vue的菜鳥,寫這個demo從react入門到結束一共用了三天時間。其中邊查資料邊看文檔才寫出了這個例子,我的認爲涉及點比較全面,能夠拿來快速上手。
若是你以爲好,請給一個贊👍。感謝