本文主要寫一些CRA腳手架的安裝,React的語法,組件分類和組件傳值等;若是您是已經在React上有豐富經驗的開發者,歡迎指出文中有問題和能夠改進的地方,對此我將表示感謝!
這是react的官方站點React官網javascript
本文將主要分爲:css
閱讀全文可能會花費您10-20分鐘,若是以爲有興趣,能夠一塊兒敲敲代碼
(使用編輯器:VSCODE,插件:VS Code ES7 React/Redux/React-Native/JS snippets
,這個插件能夠快速構建組件格式,若是想要練練手的同窗就不要用快速指令了哦)html
First thing first,這裏咱們利用create-react-app(須要nodejs環境)來建立這個項目,畢竟比較方便嘛,有其餘建立項目和服務的方式也可使用。vue
找一個工做文件夾,而後打開命令行工具,輸入create-react-app mycode
就能夠建立一個文件夾爲
mycode
的項目文件夾,注意哦,這個項目名稱不支持大寫字母
npm start
開啓開發服務器,通常默認是3000端口,啓動後會自動彈出localhost:3000
的頁面npm run build
爲生產環境建立打包的靜態文件npm test
開啓測試,這個我沒有用過,有用過的同窗能夠在評論裏分享一下使用技術文章npm run eject
Eject 將全部的工具(配置文件和 package.json 依賴庫)解壓到應用所在的路徑,這個過程是不可逆的那咱們開始吧,cd mycode
& npm start
。java
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中有什麼,有多少層級,有多少組件,有多少邏輯,最終只有這一個入口。咱們將腳手架src下的全部文件所有刪掉,建立一個空白的index.js
,開始coding。node
index.js
中,咱們要作的就是,引入React庫,引入react-dom,引入根組件,而後執行根組件的渲染方法:react
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的核心渲染方法,後面咱們會一直用到按照上一節的引入各類庫,咱們能夠在index.js中coding來學習React的基礎語法。
簡要來講,react的核心語言是jsx,按照官方文檔的舉例:ios
const name = 'Josh Perez';
const element = <h1>Hello, {name}</h1>;
ReactDOM.render(
element,
document.getElementById('root')
);
複製代碼
咱們在React下寫的<h1><h1>
這樣的html標籤,實際上都會按照上面的代碼渲染到頁面上,只不過做爲一個很sweet的語法糖,咱們不須要再寫ReactDOM.render()
,而是git
let el = <h1>hello <span>world</span></h1>
render(el,window.root);
複製代碼
使用javascript + xml語法,定義一個元素,而後再render
渲染就能夠了;在index.js中,你能夠先註釋掉本來的render(<App></App>, window.root)
這句話,改成上面的代碼,在localhost:3000中能夠看到es6
用一句話來歸納就是: 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>
)
複製代碼
頁面:
let arr = [1,2,3];
//
let el = (
arr.map((item, key) => (
<li key={key}>{item}</li>
))
)
複製代碼
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的魅力吧。
在react項目中,基本上全部的結構功能均可以拆分紅很細的一個個組件,好比一個頁面上經常使用的菜單欄,能夠拆分紅:列表框List,列表項ListItem,列表連接Link等等,這樣的好處是:
1.複用 2.方便維護 3.提升工做效率。
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)
複製代碼
非受控組件:表單數據由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
評論組件,在實現的過程當中,咱們會討論組件間數據傳遞的方式。
首先,咱們不拆分組件,將上述的例子簡單構建出來,頁面結構使用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;
複製代碼
效果:
到這裏,咱們的代碼實現的功能有,加一條評論,也能夠刪除一條評論。state = {
users: [],
count: 0,
id: 3
}
複製代碼
state中users
的數據,會在componentDidMount
生命週期時,獲取到users列表,並經過this.setState({ users });
方法更新視圖。其餘的操做,相似於handleSubmit
和removeById
一樣都是經過操做state.users
的數據達到增刪的目的。
考慮到一個項目中的複雜度,咱們能夠將上述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>
)
}
}
複製代碼
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
的方法,怎麼用到組件上呢?下面咱們進行講解。
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來實現(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。 但願個人文章能幫到你。