本文提要
本文主要寫一些CRA腳手架的安裝,React的語法,元件分類和元件傳值等;如果您是已經在React上有豐富經驗的開發者,歡迎指出文中有問題和可以改進的地方,對此我將表示感謝!
這是react的官方站點React官網
本文將主要分為:
- React專案搭建
- React語法
- React元件介紹
- React元件傳值
- 一個簡單的元件化小例子
- 本文暫時不介紹React-Router
閱讀全文可能會花費您10-20分鐘,如果覺得有興趣,可以一起敲敲程式碼
(使用編輯器:VSCODE,外掛:VS Code ES7 React/Redux/React-Native/JS snippets
,這個外掛可以快速構建元件格式,如果想要練練手的同學就不要用快速指令了哦)
下面開始正文
1.通過腳手架建立React專案
First thing first,這裡我們利用create-react-app(需要nodejs環境)來建立這個專案,畢竟比較方便嘛,有其他建立專案和服務的方式也可以使用。
找一個工作資料夾,然後開啟命令列工具,輸入create-react-app mycode
就可以建立一個資料夾為mycode
的專案資料夾,注意哦,這個專案名稱不支援大寫字母ok,幾分鐘後會提示你腳手架初始化成功了 這裡有幾個指令:
npm start
開啟開發伺服器,一般預設是3000埠,啟動後會自動彈出localhost:3000
的頁面npm run build
為生產環境建立打包的靜態檔案npm test
開啟測試,這個我沒有用過,有用過的同學可以在評論裡分享一下使用技術文章npm run eject
Eject 將所有的工具(配置檔案和 package.json 依賴庫)解壓到應用所在的路徑,這個過程是不可逆的
那我們開始吧,cd mycode
& npm start
。
2.基本語法
專案中兩個重要的檔案
我們啟動後會看到這個介面,這是腳手架自帶的。 請移步到編輯器,這裡我們暫時只關注兩個重要的檔案public/index.html
因為React搭建的是SPA,所以index.html是我們的主頁,在檔案中你也可以看到<div id="root"></div>
,root就是根元件渲染的位置。src/index.js
這是我們的主要的js檔案,
其中這一句表示式:render(<App></App>, window.root)
表明,我們使用一個渲染方式render
,將App
渲染到root
中去,不論App中有什麼,有多少層級,有多少元件,有多少邏輯,最終只有這一個入口。
React中的一切都從這裡開始
我們將腳手架src下的所有檔案全部刪掉,建立一個空白的index.js
,開始coding。
index.js
中,我們要做的就是,引入React庫,引入react-dom,引入根元件,然後執行根元件的渲染方法:
import React from 'react'
注意這裡的React必須首字母大寫import {Component} from 'react'
,引入元件方法,使用{Component}
解構方式引入import App from './App'
,引入根元件App,我們再下一步將會建立一個src/App.js
作為我們的根元件,這裡你可以取任何名字作為你的根元件js,我習慣取作Apprender(<App></App>, window.root)
渲染元件到root容器,render
是react的核心渲染方法,後面我們會一直用到
語法介紹
① jsx簡介
按照上一節的引入各種庫,我們可以在index.js中coding來學習React的基礎語法。
簡要來說,react的核心語言是jsx,按照官方文件的舉例:
const name = 'Josh Perez';
const element = <h1>Hello, {name}</h1>;
ReactDOM.render(
element,
document.getElementById('root')
);
複製程式碼
我們在React下寫的<h1><h1>
這樣的html標籤,實際上都會按照上面的程式碼渲染到頁面上,只不過作為一個很sweet的語法糖,我們不需要再寫ReactDOM.render()
,而是
let el = <h1>hello <span>world</span></h1>
render(el,window.root);
複製程式碼
使用javascript + xml語法,定義一個元素,然後再render
渲染就可以了;在index.js中,你可以先註釋掉原本的render(<App></App>, window.root)
這句話,改為上面的程式碼,在localhost:3000中可以看到
② <>和{}
用一句話來概括就是: jsx元素/react元素 用<號標識, 看到{ 會認為裡面包含的是js程式碼
1){}中執行js
- 變數取值
let str = '<h1>world</h1>'
let el = (
<div>
<div>{str}</div>
</div>
)
render(el,window.root);
複製程式碼
頁面效果如圖
str變數在{}中執行,div中間的內容應該是字串'<h1>world</h1>'
,而不是標籤h1
- 登出
如果我們想在程式碼中寫備註怎麼辦?是這樣嗎//
,哦不行,加個{//}
呢?哦不行,你在編輯器中可以看到
{/*hello*/}
的方式作為備註
-
字串解析為html
這麼做可能有被插入惡意執行指令碼的的風險。
在第一個小例子裡面提到'<h1>world</h1>'
會被作為字串內容渲染,但是如果確實想要作為dom展示h1呢?
這裡我們使用<div dangerouslySetInnerHTML={{ __html: str }}></div>
,這個API很長對吧,我們在容器上標註dangerouslySetInnerHTML
~危險地設定innerHTML
-
執行一個方法
function a() {
return <h3>hello function</h3>
}
let el = (
<div>
<div>{a()}</div>
</div>
)
複製程式碼
頁面:
- 迴圈
新增一個li key的要時最好不要用陣列的索引,為了能標示每個迴圈元素便於dom-diff, 一般用id,這裡由於例子比較簡單,我們使用陣列的索引填寫key ;使用arr.map的方式處理陣列返回
let arr = [1,2,3];
//
let el = (
arr.map((item, key) => (
<li key={key}>{item}</li>
))
)
複製程式碼
- jsx中html屬性的幾個特點
-
class
→className
這個駝峰方式的寫法代替了原生html的class,但是class還是可以用的,腳手架會提示你這裡應當使用className
-
for
→htmlFor
這個for是html的label上的for,用於指向控制的input,在jsx中我們使用htmlFor來替代
<label htmlFor="username">使用者名稱</label>
<input type="text" id="username" />
複製程式碼
-
<div style="color:red">hello</div>
→<div style={{ color: 'red' }}>hello</div>
外層的{}
表示js語法的標示,{ color: 'red' }
是物件 -
React.Fragment
如果有用過vue的同學應該知道,vue返回的html一定要有一個根節點包裹,即返回的dom一定是一個,不能是平級的多個,即
<div>
<div></div>
<p></p>
</div>
複製程式碼
在react中同樣,如果我們返回平級的多個div的話:
react會提示語法錯誤:jsx必須被一個閉合標籤包裹但是如果我們在某種情況下,必須使用一些平級元素怎麼辦呢,比如處於樣式的考慮,我們外層沒有什麼需要div包裹的。這時候我們使用<React.Fragment>
來包裹平級的元素,這個<React.Fragment>
是沒有實際意義的,就充當一個節點閉合標籤。
let el1 = (
<React.Fragment>
<div>{str1}</div>
<div>{a()}</div>
<div>{JSON.stringify(obj)}</div>
<div>{false?<span>你好</span>:void 0}</div>
</React.Fragment>
)
複製程式碼
這樣就不會報錯了
總的來說,react的API較少,寫jsx是很自由的,js+xml的方式,使js功底很深厚的開發者可以在html中任意的書寫js邏輯,所寫即所得,可能這就是react的魅力吧。
3.元件
在react專案中,基本上所有的結構功能都可以拆分成很細的一個個元件,比如一個頁面上常用的選單欄,可以拆分成:列表框List,列表項ListItem,列表連結Link等等,這樣的好處是:
1.複用 2.方便維護 3.提高工作效率。
react宣告元件的方式分為函式宣告和類宣告
- 函式式宣告元件
函式式宣告元件的方式如下
function Build(props) {
let {title,content} = props;
return (
<div>
<div>{title}</div>
<div>{content}</div>
</div>
)
}
render(<div>
<Build title="build1" content="content1"></Build>
<Build title="build2" content="content2"></Build>
<Build title="build3" content="content3"></Build>
</div>, window.root);
複製程式碼
如果我們僅需要展示一些資訊到頁面上,不需要去控制變化,則函式元件可以簡單實現
元件的定義一定要是首字母大寫的,函式式元件傳值的方式是按照在元件中定義了屬性名,在元件使用時直接寫在元件上 <Build title="build3" content="content3"></Build>
函式元件的缺點是 1.沒有this 2.沒有狀態 3.沒有宣告週期
可以通過定時器可以實現函式式元件中值的定時改變,比如這個例子
function Clock(props) {
return <div> 時間更新:<span>{props.time}</span></div>
}
setInterval(()=>{
render(<Clock time={new Date().toLocaleString()} />, window.root);
},1000)
複製程式碼
- 類宣告
使用es6建立類的方式建立元件,類宣告的元件擁有了狀態,檢視通過setState方法進行更新 之後我們做的小例子的元件,都用類宣告的方式進行建立。在使用中來學習使用
受控元件 和 非受控元件
非受控元件:表單資料由DOM本身處理。即不受setState()的控制,與傳統的HTML表單輸入相似,input輸入值即顯示最新值(使用 ref 從DOM獲取表單值)
受控元件:在HTML中,標籤<input>
、<textarea>
、<select>
的值的改變通常是根據使用者輸入進行更新。在React中,可變狀態通常儲存在元件的狀態屬性中,並且只能使用 setState() 更新,而呈現表單的React元件也控制著在後續使用者輸入時該表單中發生的情況,以這種由React控制的輸入表單元素而改變其值的方式,稱為:“受控元件”。
這裡我們寫一個非受控元件的小例子,我們輸入的值通過點選顯示出來;非受控元件常用於操作dom,較為方便
import React,{Component} from 'react';
import {render} from 'react-dom';
class UnControl extends Component{
b=React.createRef();
handleClick = () =>{
alert(this.a.value); // 寫法1
alert(this.b.current.value) // 寫法2
}
render(){
return (<div>
<input type="text" id="username" ref={dom=>this.a=dom}/>
<input type="text" id="password" ref={this.b}/>
<button onClick={this.handleClick}>點選</button>
</div>)
}
}
render(<UnControl></UnControl>, window.root);
複製程式碼
接下來我們將實現這樣的一個小例子:
實現一個評論元件,類似於掘金下方的評論欄,我們將這個元件大功能拆分為
根元件App
,列表元件List
,列表項ListItem
和Comment
評論元件,在實現的過程中,我們會討論元件間資料傳遞的方式。
4.更新檢視的方法
首先,我們不拆分元件,將上述的例子簡單構建出來,頁面結構使用bootstrap UI(npm install boostrap@3
) 元件。
在這個例子中,我們採用axios(npm install axios
)請求初始列表資料,封裝為一個request.js,程式碼如下:
import axios from 'axios';
axios.interceptors.response.use(function (res) {
if (res.data.code === 0) {
return res.data.users
} else {
return Promise.reject('錯誤');
}
})
export default axios
複製程式碼
請求的資料格式自己簡單擬定為:
{
"code":0,
"users": [
{
"id": 1,
"avatar": "http://05.imgmini.eastday.com/mobile/20171112/20171112104845_40c4a989ba5a02f512b05336bff309f8_1.jpeg",
"username": "Jim",
"content": "Hi,你的文章很不錯"
},
{
"id": 2,
"avatar": "http://05.imgmini.eastday.com/mobile/20171112/20171112104845_40c4a989ba5a02f512b05336bff309f8_1.jpeg",
"username": "Jim",
"content": "一般般的說"
}
]
}
複製程式碼
然後貼出我們的App.js,我們將全部的內容都放在App.js中,不拆分元件:
import React, { Component } from 'react';
import axios from './request'
import 'bootstrap/dist/css/bootstrap.css'
import './Common/common.css'
class App extends Component {
state = {
users: [],
count: 0,
id: 3
}
// 點贊功能
increment = () => {
this.setState({
count: this.state.count + 1
})
}
// 新增評論
addComment = (val) => {
let id = this.state.id;
let users = [...this.state.users, { avatar: "http://05.imgmini.eastday.com/mobile/20171112/20171112104845_40c4a989ba5a02f512b05336bff309f8_1.jpeg", content: val, username: 'Jim', id: id}];
this.setState({
users
});
this.state.id+=1;
}
content = React.createRef();
// 提交資料
handleSubmit = (e) => {
e.preventDefault();
this.addComment(this.content.current.value);
}
// 刪除一條
removeById = (id) => {
let users = this.state.users.filter(user=>user.id!==id); // 排除列表裡相同id的,即達到刪除的目的
this.setState({
users
})
}
// 獲取列表資料
async componentDidMount() {
let users = await axios.get('/users.json');
this.setState({
users
});
}
render() {
return (
<div className="container">
<div className="panel panel-danger">
<div className="panel-heading">
評論
</div>
<div className="panel-body">
{
this.state.users.map((user, index) => {
return (
<div className="media">
<div className="media-left">
<img className="avatar" src={user.avatar} />
</div>
<div className="media-right">
<h3>{user.username} </h3>
<div>評論:{user.content}</div>
<button className="btn btn-danger" onClick={(e)=>{
this.removeById(user.id)
}}>刪除</button>
</div>
</div>
)
})
}
</div>
<div className="panel-bottom">
<form onSubmit={this.handleSubmit}>
<textarea className="form-control" required ref={this.content}></textarea>
<button type="submit" >評論</button>
</form>
</div>
</div>
</div>
);
}
}
export default App;
複製程式碼
效果:
到這裡,我們的程式碼實現的功能有,加一條評論,也可以刪除一條評論。在React中,檢視是受到資料的驅動的,我們最初定義的
state = {
users: [],
count: 0,
id: 3
}
複製程式碼
state中users
的資料,會在componentDidMount
生命週期時,獲取到users列表,並通過this.setState({ users });
方法更新檢視。其他的操作,類似於handleSubmit
和removeById
同樣都是通過操作state.users
的資料達到增刪的目的。
5.拆分元件
考慮到一個專案中的複雜度,我們可以將上述App.js中的相關內容進行拆分為:列表元件List
,列表項ListItem
和Comment
評論元件,這樣,構造其他結構的時候,我們就不用再去重新寫一遍相同的程式碼。我們在src資料夾下新建components
資料夾,並且建立List.js
ListItem.js
Comment.js
- 列表項元件:
import React, { Component } from 'react'
export default class ListItem extends Component {
state = {
users: [],
id: 100000
}
addComment = (val) => {
let id = this.state.id;
let users = [...this.state.users, { avatar: "http://05.imgmini.eastday.com/mobile/20171112/20171112104845_40c4a989ba5a02f512b05336bff309f8_1.jpeg", content: val, username: 'Jim', id: id}];
this.setState({
users
});
this.state.id+=1;
}
handleClick = (id) => {
this.props.removeById(id);
}
removeById = (id) => {
let users = this.state.users.filter(user=>user.id!==id); // 排除列表裡相同id的,即達到刪除的目的
this.setState({
users
})
}
render() {
let {id, avatar, content, username} = this.props;
return (
<div className="media">
<div className="media-left">
<img className="avatar" src={avatar} />
</div>
<div className="media-right">
<h3>{username} {id}</h3>
<div>評論:{content}</div>
<button className="btn btn-danger" onClick={(e)=>{
this.handleClick(id)
}}>刪除</button>
</div>
</div>
)
}
}
複製程式碼
- 列表元件
import React, { Component } from 'react'
import ListItem from './ListItem'
export default class List extends Component {
static props = {
showComment: true
}
render() {
return (
<div>
{
this.props.users.map((user, index) => {
return (
<ListItem showComment={this.props.showComment} {...user} key={index} removeById={this.props.removeById} addComment={this.props.addComment}></ListItem>
)
})
}
</div>
)
}
}
複製程式碼
- 評論框元件
import React, { Component } from 'react'
export default class Comment extends Component {
content = React.createRef();
handleSubmit = (e) => {
e.preventDefault();
this.props.addComment(this.content.current.value);
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<textarea className="form-control" required ref={this.content}></textarea>
<button type="submit" >評論</button>
</form>
)
}
}
複製程式碼
- App.js變為
import React, { Component } from 'react';
import axios from './request'
import 'bootstrap/dist/css/bootstrap.css'
import './Common/common.css'
import Comment from './components/Comment'
import List from './components/List'
import {Provider} from './context'
class App extends Component {
state = {
users: [],
count: 0,
id: 3
}
increment = () => {
this.setState({
count: this.state.count + 1
})
}
addComment = (val) => {
let id = this.state.id;
let users = [...this.state.users, { avatar: "http://05.imgmini.eastday.com/mobile/20171112/20171112104845_40c4a989ba5a02f512b05336bff309f8_1.jpeg", content: val, username: 'Jim', id: id}];
this.setState({
users
});
this.state.id+=1;
}
removeById = (id) => {
console.log(id)
let users = this.state.users.filter(user=>user.id!==id); // 排除列表裡相同id的,即達到刪除的目的
this.setState({
users
})
}
async componentDidMount() {
let users = await axios.get('/users.json');
this.setState({
users
});
}
render() {
return (
<Provider value={{increment: this.increment}}>
<div className="container">
<div className="panel panel-danger">
<div className="panel-heading">
評論
</div>
<div className="panel-body">
<List users={this.state.users} showComment={true} removeById={this.removeById} addComment={this.addComment}></List>
</div>
<div className="panel-bottom">
<br/>
<Comment addComment={this.addComment}></Comment>
獲得的贊數量{this.state.count}
</div>
</div>
</div>
</Provider>
);
}
}
export default App;
複製程式碼
看到這裡,一定有疑問,那麼我們之前定義的users
資料,removeById
和addComment
的方法,怎麼用到元件上呢?下面我們進行講解。
6.元件間屬性的傳遞
- 元件的資料互動的方式是屬性傳遞,傳遞屬性值或方法
- 子元件不能直接修改屬性值
- 但是可以通過父元件傳遞進來的方法呼叫以改變屬性值
- 資料傳遞是單向的:父->子,即常說的單項資料流
- 子元件獲取屬性的方法:`this.props.fn
- 可以使用
contextApi
實現跨元件傳遞
上一節我們拆分的元件中,在列表元件中原本的迴圈體資料來源,由this.state.users
改為了使用this.props.users
,而在App.js中傳入的方式為
<List users={this.state.users} showComment={true} removeById={this.removeById} addComment={this.addComment}></List>
複製程式碼
傳入和獲取是一一對應的。
同樣,由於ListItem
元件需要removeById
方法,所以我們從App.js
的List
元件就傳入removeById
,在List
元件中呼叫ListItem
時,再次傳入ListItem
,是一個父傳子,子傳孫的過程:
<ListItem showComment={this.props.showComment} {...user} key={index} removeById={this.props.removeById} addComment={this.props.addComment}></ListItem>
複製程式碼
在ListItem
元件中,我們對removeById
方法再包裝一層
handleClick = (id) => {
this.props.removeById(id);
}
...
<button className="btn btn-danger" onClick={(e)=>{
this.removeById(user.id)
}}>刪除</button>
複製程式碼
這裡我們的刪除方法來自於根元件傳遞下來的方法,子元件獲取後,對同樣是傳遞進來的users
進行修改,以到達改變資料的目的。以上就是簡單的元件傳值的講解。
contextApi
如果我們想給這個列表加一個點贊功能,即任何一個列表項元件都可以點贊,而且點贊還可以收集總數,這時候如果再去用父子間元件傳值,可能程式碼實現起來會比較麻煩或者易錯,因為涉及的層級很多。所以我們利用contextApi來實現(react16.3)。
引入的方式(在例子中,我抽離了這個引入到context.js,就不用在每個頁面寫一遍解構了):
import React from 'react'
let {Provider, Consumer} = React.createContext();
export {Provider, Consumer}
複製程式碼
在元件中的使用方法是,在父元件引入後,將父元件的返回值使用Provider
包裹,並傳入value
屬性:
import React, { Component } from 'react';
import {Provider} from './context'
class App extends Component {
state = {
users: [],
count: 0,
id: 3
}
// 點贊功能
increment = () => {
this.setState({
count: this.state.count + 1
})
}
render() {
return (
<Provider value={{increment: this.increment}}>
<div className="container">
<div className="panel panel-danger">
<div className="panel-heading">
評論
</div>
<div className="panel-body">
<List users={this.state.users} showComment={true} removeById={this.removeById} addComment={this.addComment}></List>
</div>
<div className="panel-bottom">
<br/>
<Comment addComment={this.addComment}></Comment>
獲得的贊數量{this.state.count}
</div>
</div>
</div>
</Provider>
);
}
}
export default App;
複製程式碼
在子元件中,需要使用(消費)的返回值外層包裹Consumer
,使用箭頭函式傳入value
的值,即Provider
傳入的屬性,即可在元件中直接呼叫父元件或更高階的元件的傳入屬性。
import React, { Component } from 'react'
import {Consumer} from '../context'
...
export default class ListItem extends Component {
...
render() {
let {id, avatar, content, username} = this.props;
return (
<Consumer>
{(value)=>{
return <div className="media">
<div className="media-right">
...
<button className="btn btn-primary" onClick={()=>{
value.increment()
}}>贊</button>
...
</div>
</div>
}}
</Consumer>
)
}
}
複製程式碼
總結
以上是我學習React入門的一些小總結,寫了一個不太成熟的例子來練手,在表述上可能有一些跳躍還請見諒。這裡附上這個小例子的Github程式碼,有需要詳細瞭解的同學可以看看:code。
希望我的文章能幫到你。