前言
- React 中使用了 JSX 語法糖,是一種可以將 HTML 和 JS 揉著寫的語法糖;
- 瀏覽器不能直接運行 JSX 語法糖,需要使用 babel 來翻譯;
- 如果使用了 babel,就可以寫 ES6 代碼了;
- 寫一點翻譯一點非常不方便,所以要用 webpack 結(jié)合 babel-loader 來 watch 文件;
- 既然使用了 webpack,那我們就可以進(jìn)行模塊化開發(fā)了。
創(chuàng)建項目
-
創(chuàng)建一個項目文件夾,在這個文件夾中配置 webpack + babel 環(huán)境,讓 webpack 可以指導(dǎo) babel 翻譯 ES6 語法:
- 創(chuàng)建 package.json:
cnpm init;
- 創(chuàng)建 package.json:
-
安裝 webpack,并且設(shè)置為項目依賴:
-
cnpm i --save-dev webpack;需要在全局環(huán)境中已經(jīng)安裝好 webpack。
-
創(chuàng)建
webpack.config.js文件:參照官網(wǎng):https://webpack.js.org/configuration/。
const path = require('path');
module.exports = {
entry: "./app/main.js", // string | object | array
output: {
// options related to how webpack emits results
path: path.resolve(__dirname, "dist"), // string
// the target directory for all output files
// must be an absolute path (use the Node.js path module)
filename: "all.js", // string
}
}
創(chuàng)建完對應(yīng)的項目文件之后,在控制臺直接執(zhí)行 webpack,就可以看到 all.js 文件。
到目前,我們已經(jīng)可以進(jìn)行標(biāo)準(zhǔn)的 CMD 模塊化開發(fā)了。
- 我們再引入 babel-loader 來翻譯 ES6,然后修改 webpack.config.js 中的內(nèi)容。
const path = require('path');
module.exports = {
entry: "./app/main.js", // string | object | array
output: {
// options related to how webpack emits results
path: path.resolve(__dirname, "dist"), // string
// the target directory for all output files
// must be an absolute path (use the Node.js path module)
filename: "all.js", // string
},
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['es2015'],
plugins: [require('babel-plugin-transform-object-rest-spread')]
}
}
}
]
}
};
- 安裝 es6 插件:
cnpm i --save-dev babel-preset-es2015; - 安裝 babel-loader:
cnpm i --save-dev babel-loader;
安裝 React 并進(jìn)行配置:
- 安裝:
cnpm i --save-dev react,cnpm i --save-dev react-dom; - 配置修改:
presets: ['es2015','react'];- 運行
webpack報錯,提示缺少preset配置,進(jìn)行安裝:cnpm i --save-dev babel-preset-react;
- 運行
- 在
webpack.config.js添加設(shè)置 watch:
/**
* Created by YJW on 2018/4/2.
*/
const path = require('path');
module.exports = {
entry: "./app/main.js", // string | object | array
output: {
// options related to how webpack emits results
path: path.resolve(__dirname, "dist"), // string
// the target directory for all output files
// must be an absolute path (use the Node.js path module)
filename: "all.js", // string
},
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['es2015','react'],
plugins: [require('babel-plugin-transform-object-rest-spread')]
}
}
}
]
},
watch:true
};
- 問題:
- 為什么用 npm 安裝 React:
- 因為使用時候不是在 script 標(biāo)簽中引用,而是通過 import 進(jìn)行導(dǎo)入。
- 為什么用 npm 安裝 React:
創(chuàng)建組件
- 創(chuàng)建一個組件 App:
- 自定義組件名稱一定要大寫;
- React 要求自定義組件的類必須繼承于 React.Component 類;
- render:渲染方法,直接調(diào)用,返回一個 JSX 語法,非常牛逼的語法。
- App.js 中的內(nèi)容如下:
import React from "react";
class App extends React.Component{
render(){
return (<h1>哈哈哈123</h1>);
}
}
export default App;
或者使用如下方式:({}:自動解構(gòu)、枚舉;如果沒有使用 {},就是 default 暴露的)
import React,{Component} from "react";
class App extends Component{
render(){
return <h1>嘿嘿嘿</h1>
}
}
export default App;
- 創(chuàng)建一個 main.js 文件,在該文件中使用組件:
- 使用、掛載組件,有兩個參數(shù);
- 第一個參數(shù)是 JSX 語法;
- 第二個參數(shù)表示組件掛載到哪里。
import React from "react";
import {render} from "react-dom";
import App from "./App";
render(
<App/>,
document.getElementById("yjw11")
);
- 在 demo.html 中引入 all.js。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>demo</title>
</head>
<body>
<div id="yjw11"></div>
<script src="dist/all.js"></script>
</body>
</html>
這樣就實現(xiàn)的組件的創(chuàng)建與展示。
JSX 語法簡單介紹
JSX 不能直接運行,是被 babel-loader 中的 react 這個 preset 翻譯的;
-
注意:
- 如果有多個 DOM,必須被一個 DOM 包裹,然后返回;
- 標(biāo)簽必須封閉;
- class 要寫成 className,for 要寫成 htmlFor;
- html 注釋不能使用,只能使用 js 注釋;
- 在 html 原有標(biāo)簽中添加自定義屬性需要加
data-前綴,如果是自定義標(biāo)簽,屬性定義沒有特殊要求。
JSX 可以使用 {} 表示臨時插入一個 js 簡單表達(dá)式,不能是 for、if 等復(fù)雜結(jié)構(gòu),可以是 &&、|| 等邏輯關(guān)系運算符或者三元運算符等,可以調(diào)用函數(shù)。
樣式,推薦使用內(nèi)聯(lián)樣式,使用雙花括號設(shè)置:
{{}},如下所示:
class App extends Component{
render(){
var myStyle = {"width":100,"height":20,"color":"red"};
return <h1 style={myStyle}>嘿嘿嘿</h1>
}
}
- 數(shù)組:JSX 允許在模板中插入數(shù)組,數(shù)組會自動展開所有成員。
render(){
//定義一個數(shù)組,定義的 JSX 項目上要求有 key 屬性,只要是重復(fù)的數(shù)組項目,都要有不能重復(fù)的 key 屬性。
var liArr = ["aaa","bbb","ccc"].map((item,index)=>{
return <li key={index}>{item}</li>;
});
return (
<ul>{liArr}</ul>
)
}
React 中的數(shù)據(jù)傳遞
- React 中的數(shù)據(jù)傳遞三兄弟:state、props、context。
0.修改視圖
- 要求改變變量的值,并修改頁面:
import React,{Component} from "react";
class App extends Component{
constructor(){
super();
this.a = 100;
}
add(){
this.a = this.a + 1;
console.log("aaa");
console.log(this.a);
}
render(){
return (
//bind不會刺激函數(shù)運行,call 和 apply 會刺激函數(shù)運行
<h1 onClick={(this.add).bind(this)}>{this.a}</h1>
)
}
}
export default App;
上述案例中,a 的值發(fā)生了改變,但是頁面并沒有發(fā)生改變。即在 React 中,`組件自己屬性的變化不會引起視圖的變化`。閉包中的值變化不會引起視圖的變化
綁定監(jiān)聽使用 onClick、onMousedown、onMouseenter、onBlur,把 on 后面的字母大寫,React 會自動識別 React 事件;
綁定函數(shù)的時候,this 上下文是有問題的,所以要使用 bind() 方法來設(shè)置上下文;
綁定監(jiān)聽函數(shù)的時候,注意使用 {},而不是 ”“。
- 只有改變?nèi)值艿闹担拍芤?Virtual DOM 的改變,從而改變 DOM。
1.state
- 定義 state:在構(gòu)造函數(shù)中使用 this.state 屬性即可;
- 使用 state:在 JSX 中
{this.state.a}; - 改變 state:
this.setState({a:this.state.a+1}); - state 是內(nèi)部的(也叫 local state),只有組件自己能改變自己的 state,別人不可以更改。
import React,{Component} from "react";
class App extends Component{
constructor(){
super();
this.state = {
a:100,
b:200,
c:300
}
}
add(){
this.setState({a:this.state.a + 1})
}
render(){
return (
<div>
<p>我這里有 state</p>
<input type="button" value="點我加 1" onClick={(this.add).bind(this)}/>
<p>state 的值:{this.state.a}</p>
</div>
)
}
}
2.props
- 利用 props 使父組件向子組件傳值,又因為此屬性是只讀的,所以只能單向綁定:
- main.js:
import React from "react";
import {render} from "react-dom";
import App from "./App";
render(
<App tt={111}/>,
document.getElementById("yjw")
);
- App.js:
import React,{Component} from "react";
class App extends Component{
render(){
return (
<p>props 的值:{this.props.tt}</p>
)
}
}
export default App;
- 如果需要在子組件的構(gòu)造函數(shù)中,使用 props,只需要在構(gòu)造函數(shù)中接收一個參數(shù)(第一個參數(shù)是 props,第二個參數(shù)是 context(下面會講到)):
import React,{Component} from "react";
class App extends Component{
constructor(haha){
super();
alert(haha.tt)
}
render(){
return (
<p>props 的值:{this.props.tt}</p>
)
}
}
export default App;
- 如果需要在子組件中對 props 的值進(jìn)行更改,可以配合 state 實現(xiàn):
import React,{Component} from "react";
class App extends Component{
constructor(haha){
super();
this.state = {
tt : haha.tt
}
}
add(){
this.setState({tt:this.state.tt+1})
}
render(){
return (
<p onClick={(this.add).bind(this)}>props 的值:{this.state.tt}</p>
)
}
}
export default App;
props 的屬性可以被驗證有效性:
- 安裝:
cnpm i --save-dev prop-types; - main.js 文件中的內(nèi)容:
import React from "react";
import {render} from "react-dom";
import App from "./App";
render(
<App tt={111} t2="abc" t3={22}/>,
document.getElementById("yjw")
);
- App.js 文件中的內(nèi)容:
- 類名.propTypes,值是一個 JSON,key 就是需要傳進(jìn)來的 props 屬性名,value 就是對它的限制。
import React,{Component} from "react";
import {PropTypes} from "prop-types";
class App extends Component{
render(){
return (
<p>props 的值:{this.props.tt}</p>
)
}
}
App.propTypes = {
tt:PropTypes.number.isRequired,
t2:PropTypes.string.isRequired,
t3:PropTypes.number
};
export default App;
子組件向父組件傳值
- 如果非要從下到上傳輸數(shù)據(jù):子組件把數(shù)據(jù)傳送給父組件,此時只能使用奇淫技巧,就是父組件傳一個函數(shù)給子組件,子組件通過傳參數(shù)將數(shù)據(jù)返回給父組件,父組件的函數(shù)接受實參改變父組件中的 state 等值。
- 父組件中的代碼,F(xiàn)a.js:
import React,{Component} from "react";
import Son from "./Son";
class Fa extends Component{
constructor(){
super();
this.state = {
num1:111
}
}
add(number){
this.setState({"num1":number});
}
render(){
return (
<div>
<Son num1={this.state.num1} add={(this.add).bind(this)}/>
<h2>這里是父組件:{this.state.num1}</h2>
</div>
)
}
}
export default Fa;
- 子組件中的代碼,Son.js:
import React,{Component} from "react";
class Son extends Component{
constructor(props){
super();
this.state = {
num1:props.num1
};
this.add = ()=>{
this.setState({"num1":this.state.num1+1});
props.add(this.state.num1 + 1)
}
}
render(){
console.log(this.state);
return <h1 onClick={(this.add).bind(this)}>我是子組件:{this.state.num1}</h1>
}
}
export default Son;
3.context
- 上下文的精髓是可以跨級傳遞數(shù)據(jù),爺爺組件可以直接傳遞數(shù)據(jù)給孫子。
正常傳值
- 爺爺組件,Grand.js:
import React,{Component} from "react";
import Fa from "./Fa";
class Grade extends Component{
constructor(){
super();
this.state = {
a:100
}
}
render(){
return (
<div>
<h1>爺爺</h1>
<Fa a={this.state.a}/>
</div>
)
}
}
export default Grade;
- 爸爸組件,F(xiàn)a.js:
import React,{Component} from "react";
import Son from "./Son";
class Fa extends Component{
render(){
return (
<div>
<h2>爸爸</h2>
<Son a={this.props.a}/>
</div>
)
}
}
export default Fa;
- 孫子組件,Son.js:
import React,{Component} from "react";
class Son extends Component{
render(){
return (
<div>
<h3>孫子:{this.props.a}</h3>
</div>
)
}
}
export default Son;
使用 context
- 在要傳數(shù)據(jù)的上級實例對象中要設(shè)置”得到孩子上下文“:
getChildContext(){
return {
a:this.state.a
}
}
- 在要傳數(shù)據(jù)的上級設(shè)置孩子上下文內(nèi)容類型
childContextTypes:
Grade.childContextTypes = {
a:PropTypes.number.isRequired
};
- 在要獲取數(shù)據(jù)的下級要設(shè)置上下文內(nèi)容類型
contextTypes:
Son.contextTypes = {
a:PropTypes.number
};
-
結(jié)論:
- 當(dāng)上級元素中更改了上下文的數(shù)據(jù),此時所有的下級元素中的數(shù)據(jù)都會發(fā)生改變,視圖也會更新;
- 反之不然,下級元素中數(shù)據(jù)改變,上級元素中的數(shù)據(jù)不會發(fā)生改變??梢哉J(rèn)為上下文中的數(shù)據(jù)在下級元素中是只讀的。此時如果需要在下級元素中修改上級元素中的數(shù)據(jù),就需要在 context 中共享一個操作上級元素的方法,子孫元素通過上下文獲得這個函數(shù),從而操作上級元素的值。
- state 是自治的不涉及傳值的事兒;props 是單向的,上級 --> 下級;context 也是單向的,上級 --> 下級。如果要反向,就需要傳入一個函數(shù)。
爺爺組件,Grade.js:
import React,{Component} from "react";
import Fa from "./Fa";
import PropTypes from "prop-types"
class Grade extends Component{
constructor(){
super();
this.state = {
a:100
}
}
addA(){
this.setState({a:this.state.a+1})
}
render(){
return (
<div>
<h1 onClick={()=>{this.setState({a:this.state.a+1})}}>爺爺:{this.state.a}</h1>
<Fa/>
</div>
)
}
//得到孩子上下文,實際上這里表示一種設(shè)置
getChildContext(){
return {
a:this.state.a,
addA:(this.addA).bind(this)
}
}
}
Grade.childContextTypes = {
a:PropTypes.number.isRequired,
addA:PropTypes.func.isRequired
};
export default Grade
- 爸爸組件,F(xiàn)a.js:
import React,{Component} from "react";
import Son from "./Son";
import PropTypes from "prop-types";
class Fa extends Component{
render(){
return (
<div>
<h2>爸爸</h2>
<Son/>
</div>
)
}
}
export default Fa;
- 孫子組件,Son.js:
import React,{Component} from "react";
import PropTypes from "prop-types";
class Son extends Component{
constructor(props,context){
super();
console.log(context.addA);
}
render(){
return (
<div>
<h3 onClick={this.context.addA}>孫子:{this.context.a}</h3>
</div>
)
}
}
Son.contextTypes = {
a:PropTypes.number,
addA:PropTypes.func
};
export default Son;
- context 使用頻率不高,傳值基本使用 props,除非很深的上下級進(jìn)行傳值才會使用。