前言
- React 中使用了 JSX 语法糖,是一种可以将 HTML 和 JS 揉着写的语法糖;
- 浏览器不能直接运行 JSX 语法糖,需要使用 babel 来翻译;
- 如果使用了 babel,就可以写 ES6 代码了;
- 写一点翻译一点非常不方便,所以要用 webpack 结合 babel-loader 来 watch 文件;
- 既然使用了 webpack,那我们就可以进行模块化开发了。
创建项目
-
创建一个项目文件夹,在这个文件夹中配置 webpack + babel 环境,让 webpack 可以指导 babel 翻译 ES6 语法:
- 创建 package.json:
cnpm init
;
- 创建 package.json:
-
安装 webpack,并且设置为项目依赖:
-
cnpm i --save-dev webpack
;需要在全局环境中已经安装好 webpack。
-
创建
webpack.config.js
文件:参照官网: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
}
}
创建完对应的项目文件之后,在控制台直接执行 webpack,就可以看到 all.js 文件。
到目前,我们已经可以进行标准的 CMD 模块化开发了。
- 我们再引入 babel-loader 来翻译 ES6,然后修改 webpack.config.js 中的内容。
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 并进行配置:
- 安装:
cnpm i --save-dev react
,cnpm i --save-dev react-dom
; - 配置修改:
presets: ['es2015','react']
;- 运行
webpack
报错,提示缺少preset
配置,进行安装:cnpm i --save-dev babel-preset-react
;
- 运行
- 在
webpack.config.js
添加设置 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 标签中引用,而是通过 import 进行导入。
- 为什么用 npm 安装 React:
创建组件
- 创建一个组件 App:
- 自定义组件名称一定要大写;
- React 要求自定义组件的类必须继承于 React.Component 类;
- render:渲染方法,直接调用,返回一个 JSX 语法,非常牛逼的语法。
- App.js 中的内容如下:
import React from "react";
class App extends React.Component{
render(){
return (<h1>哈哈哈123</h1>);
}
}
export default App;
或者使用如下方式:({}:自动解构、枚举;如果没有使用 {},就是 default 暴露的)
import React,{Component} from "react";
class App extends Component{
render(){
return <h1>嘿嘿嘿</h1>
}
}
export default App;
- 创建一个 main.js 文件,在该文件中使用组件:
- 使用、挂载组件,有两个参数;
- 第一个参数是 JSX 语法;
- 第二个参数表示组件挂载到哪里。
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>
这样就实现的组件的创建与展示。
JSX 语法简单介绍
JSX 不能直接运行,是被 babel-loader 中的 react 这个 preset 翻译的;
-
注意:
- 如果有多个 DOM,必须被一个 DOM 包裹,然后返回;
- 标签必须封闭;
- class 要写成 className,for 要写成 htmlFor;
- html 注释不能使用,只能使用 js 注释;
- 在 html 原有标签中添加自定义属性需要加
data-
前缀,如果是自定义标签,属性定义没有特殊要求。
JSX 可以使用 {} 表示临时插入一个 js 简单表达式,不能是 for、if 等复杂结构,可以是 &&、|| 等逻辑关系运算符或者三元运算符等,可以调用函数。
样式,推荐使用内联样式,使用双花括号设置:
{{}}
,如下所示:
class App extends Component{
render(){
var myStyle = {"width":100,"height":20,"color":"red"};
return <h1 style={myStyle}>嘿嘿嘿</h1>
}
}
- 数组:JSX 允许在模板中插入数组,数组会自动展开所有成员。
render(){
//定义一个数组,定义的 JSX 项目上要求有 key 属性,只要是重复的数组项目,都要有不能重复的 key 属性。
var liArr = ["aaa","bbb","ccc"].map((item,index)=>{
return <li key={index}>{item}</li>;
});
return (
<ul>{liArr}</ul>
)
}
React 中的数据传递
- React 中的数据传递三兄弟: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不会刺激函数运行,call 和 apply 会刺激函数运行
<h1 onClick={(this.add).bind(this)}>{this.a}</h1>
)
}
}
export default App;
上述案例中,a 的值发生了改变,但是页面并没有发生改变。即在 React 中,`组件自己属性的变化不会引起视图的变化`。闭包中的值变化不会引起视图的变化
绑定监听使用 onClick、onMousedown、onMouseenter、onBlur,把 on 后面的字母大写,React 会自动识别 React 事件;
绑定函数的时候,this 上下文是有问题的,所以要使用 bind() 方法来设置上下文;
绑定监听函数的时候,注意使用 {},而不是 ”“。
- 只有改变三兄弟的值,才能引起 Virtual DOM 的改变,从而改变 DOM。
1.state
- 定义 state:在构造函数中使用 this.state 属性即可;
- 使用 state:在 JSX 中
{this.state.a}
; - 改变 state:
this.setState({a:this.state.a+1})
; - state 是内部的(也叫 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;
- 如果需要在子组件的构造函数中,使用 props,只需要在构造函数中接收一个参数(第一个参数是 props,第二个参数是 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 的值进行更改,可以配合 state 实现:
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 文件中的内容:
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 文件中的内容:
- 类名.propTypes,值是一个 JSON,key 就是需要传进来的 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;
子组件向父组件传值
- 如果非要从下到上传输数据:子组件把数据传送给父组件,此时只能使用奇淫技巧,就是父组件传一个函数给子组件,子组件通过传参数将数据返回给父组件,父组件的函数接受实参改变父组件中的 state 等值。
- 父组件中的代码,Fa.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
- 上下文的精髓是可以跨级传递数据,爷爷组件可以直接传递数据给孙子。
正常传值
- 爷爷组件,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;
- 爸爸组件,Fa.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
- 在要传数据的上级实例对象中要设置”得到孩子上下文“:
getChildContext(){
return {
a:this.state.a
}
}
- 在要传数据的上级设置孩子上下文内容类型
childContextTypes
:
Grade.childContextTypes = {
a:PropTypes.number.isRequired
};
- 在要获取数据的下级要设置上下文内容类型
contextTypes
:
Son.contextTypes = {
a:PropTypes.number
};
-
结论:
- 当上级元素中更改了上下文的数据,此时所有的下级元素中的数据都会发生改变,视图也会更新;
- 反之不然,下级元素中数据改变,上级元素中的数据不会发生改变。可以认为上下文中的数据在下级元素中是只读的。此时如果需要在下级元素中修改上级元素中的数据,就需要在 context 中共享一个操作上级元素的方法,子孙元素通过上下文获得这个函数,从而操作上级元素的值。
- state 是自治的不涉及传值的事儿;props 是单向的,上级 --> 下级;context 也是单向的,上级 --> 下级。如果要反向,就需要传入一个函数。
爷爷组件,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>
)
}
//得到孩子上下文,实际上这里表示一种设置
getChildContext(){
return {
a:this.state.a,
addA:(this.addA).bind(this)
}
}
}
Grade.childContextTypes = {
a:PropTypes.number.isRequired,
addA:PropTypes.func.isRequired
};
export default Grade
- 爸爸组件,Fa.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,除非很深的上下级进行传值才会使用。