一. 使用 create-react-app 快速构建 React 开发环境
npm install -g create-react-app
create-react-app my-app
cd my-app/
npm start
你也可以直接使用 Staticfile CDN 的 React CDN 库,地址如下:
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<!-- 生产环境中不建议使用 -->
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
二. React打包
react-app-rewired使用
它的作用是用来帮助你重写react脚手架配置
react-app-rewired@2.x版本需要搭配customize-cra使用。
npm i react-app-rewired customize-cra --save-dev
在根目录下新建文件config-overrides.js文件
const {
override,
addDecoratorsLegacy,
disableEsLint,
} = require("customize-cra");
module.exports = {
webpack: override(
addDecoratorsLegacy(),
disableEsLint(),
)
};
修改package.json文件
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-scripts eject"
},
打开src文件夹,里面有个index.js和app.js文件,其中index.js是主入口文件,app.js就是页面展示建议react组件,
三. React路由
npm install react-router react-router-dom
在app.js中导入
import {
Switch,
HashRouter as Router,
// BrowserRouter as Router,
Route,
Link
} from 'react-router-dom';
调用方法一
<Router>
{/*只显示某一页的内容 http://localhost:3000/#/或http://localhost:3000/#/help*/}
<Switch>
<Route path="/" exact component={home} />
<Route path="/help" component={help}/>
</Switch>
</Router>
调用方法二
<Router>
<div>
<Route exact path="/" component={home}/>
<Route path="/help" component={help}/>
</div>
</Router>
四. 创建组件Table.js
新建组件注意事项
1、Component
作为一个组件引入了,不需要再执行React.Component
2、使用class继承,元素必须在render()
方法里面返回
3、样式类名书写是className,不是class
4、一定要将组件导出export default componentName
5、react组件名必须大写字母开头
6、只能return一个根元素,不能return两个根元素,也就是说renturn的标签必须包裹在一个根标签里面,不能是两个同级标签
import React, { Component } from 'react'
class Table extends Component {
render() {
return (
<table>
<thead>
<tr>
<td>Name</td>
<td>Job</td>
</tr>
</thead>
<tbody>
<tr>
<td>李狗蛋</td>
<td>程序猿</td>
</tr>
<tr>
<td>王翠花</td>
<td>攻城狮</td>
</tr>
</tbody>
</table>
)
}
}
export default Table
组件写好后,在app.js里面引入组件并使用
<Router>
<div>
<Route exact path="/" component={home}/>
<Route path="/help" component={help}/>
<Table Head={Head}/>
</div>
</Router>
五,简单组件
通过class创建的组件可以称之为复杂组件,还可以创建简单组件,所谓的简单组件,其实用类似函数的方式声明组件,现在用简单组件把table的头部和躯体部分分别分离出来作为一个单独的小组件
简单组件跟复杂组件的区别之一就是简单组件不需要render()方法去置换一下return元素,直接返回react元素
// table.js
function TableHead(props) {
return (
<thead>
<tr>
<th>Name</th>
<th>Job</th>
</tr>
</thead>
)
}
function TableBody(props) {
return (
<tbody>
<tr>
<td>李狗蛋</td>
<td>程序猿</td>
</tr>
<tr>
<td>王翠花</td>
<td>攻城狮</td>
</tr>
</tbody>
)
}
调用
class Table extends Component {
render() {
return (
<table>
<TableHead />
<TableBody />
</table>
)
}
}
所有组件都是可以相互嵌套的,而且简单组件和复杂组件也是可以相互嵌套的,并没有的区别
六. 组件通信props
react中组件通信跟vue有点类似,是通过props来接收数据传递,不同的是:
1、数据是全局保存在props对象里面的,直接调用props对象就可以获取
2、数据传递也不需要通过v-bind
来绑定参数,直接写即可,只不过传入参数使用{}
包裹,而不是""
3、在简单函数里,props是作为一个参数传入的,所以直接通过props.key获取,但是在class里面,props是继承于Compoent,需要通过super()
方法,调用是通过this.props.key
因为所有组件都是在app.js里面渲染的,所以现在要在app.js里面创建数据传递过去,需要注意一点就是传递的数据必须创建在渲染组件元素的render()
函数里面,创建在render()
方法之外,是没有效果的,如果是简单组件,就直接声明一个数据数组, 数据声明好之后,直接在组件上传递
function App() {
const Head = [
{ header: 'Name' },
{header: 'Job'}
]
return (
<div className="App">
// 传递数据
<Table Head={Head} />
</div>
);
}
接下来就可以在Table组件的render函数通过es6方法从props里面拿到数据赋值给新声明的变量,注意必须在render()方法里面声明获取,简单函数直接声明获取获取
// react里面列表渲染时通过map()方法实现的,因为map()方法返回的是一个结果数组
// 需要注意的是每一个循环创建的react元素必须赋予一个key值,这是唯一标识符,同一个react元素里不能相同
const TableHead = (props) => {
const myHead = props.Head.map((item, index) => {
return <th key={index}>{item.header}</th>
})
return (
<thead>
<tr>
{myHead}
</tr>
</thead>
)
}
class Table extends Component {
render() {
// 拿取元素
const {Head} = this.props
return (
<table>
<TableHead Head={Head} />
<TableBody />
</table>
)
}
}
扩展一:
将使用单独的Body数组将tbody的值也传到table.js中
import './App.css';
import {
HashRouter as Router,
// BrowserRouter as Router,
Route,
} from 'react-router-dom';
import help from "./page/help";
import home from "./page/home";
import Table from "./page/Table";
import Table1 from "./page/Table1"
function App() {
const Head = [
{header: 'Name'},
{header: 'Job1'}
]
const Body = [
{
name: '李狗蛋',
job: '程序猿',
},
{
name: '王翠花',
job: '攻城狮',
},
{
name: '二狗子',
job: '加班狗',
}
]
return (
// 显示导航栏并在下方显示选中的页面内容
<Router>
<div>
<Route exact path="/" component={home}/>
<Route path="/help" component={help}/>
<Table Head={Head} Body={Body}/>
</div>
</Router>
);
}
export default App;
table.js
import React, {Component} from 'react'
// 方法三<TableHead Head={Head}/>
const TableHead = (props) => {
console.log('Head',props)
const myHead = props.Head.map((item,index) => {
return <th key={index}>{item.header}</th>
})
return (
<thead>
<tr>{myHead}</tr>
</thead>
)
}
const TableBody = (props) => {
console.log('Body',props)
const myBody = props.Body.map((item1,index1) => {
return <tr key={index1}>
<td>{item1.name}</td>
<td>{item1.job}</td>
</tr>
})
return (
<tbody>{myBody}</tbody>
)
}
class Table extends Component{
render() {
// 拿取传递过来的数据 <Table Head={Head} Body={Body}/>
// {Head,Body}的名字与table的属性名相同
const {Head,Body} = this.props
console.log('total', this.props)
return (
<table>
{/*二. 调用组件,相互嵌套*/}
<TableHead Head={Head}/>
<TableBody Body={Body}/>
</table>
)
}
}
export default Table
扩展二:
使用整个数组将Head和Body的值传到Table1.js中
import './App.css';
import {
HashRouter as Router,
// BrowserRouter as Router,
Route,
} from 'react-router-dom';
import help from "./page/help";
import home from "./page/home";
import Table from "./page/Table";
import Table1 from "./page/Table1"
function App() {
const state = {
Head: [
{header: 'Name'},
{header: 'Job1'}
],
Body: [
{
name: '李狗蛋',
job: '程序猿',
},
{
name: '王翠花',
job: '攻城狮',
},
{
name: '二狗子',
job: '加班狗',
}
]
}
return (
// 显示导航栏并在下方显示选中的页面内容
<Router>
<div>
<Route exact path="/" component={home}/>
<Route path="/help" component={help}/>
<Table Head={Head} Body={Body}/>
<Table1 state={state}/>
</div>
</Router>
);
}
export default App;
Table1.js
import React, {Component} from 'react'
const TableHead = (props) => {
const myHead = props.Head.map((item,index) => {
return <th key={index}>{item.header}</th>
})
return (
<thead>
<tr>{myHead}</tr>
</thead>
)
}
const TableBody = (props) => {
const myBody = props.Body.map((item,index) => {
return <tr key={index}>
<td>{item.name}</td>
<td>{item.job}</td>
</tr>
})
return (
<tbody>{myBody}</tbody>
)
}
class Table1 extends Component{
render() {
// 获取父组件定义的变量
const {state} = this.props
console.log('state', {state})
return (
<table>
<TableHead Head={state.Head}/>
<TableBody Body={state.Body}/>
</table>
)
}
}
export default Table1
运行结果:(扩展一和扩展二运行结果相同)
七:子组件向父组件传值
父组件:
import React from 'react';
import Child from "./Child";
export default class home extends React.Component{
getData=(data)=>{
console.log(data)
document.getElementById("changeData").textContent = data
}
render(){
return <div>
<p>
父组件<br/>
点击子组件,向父组件传值
{/*将传过来的值显示在id='changeData'的控件里*/}
<div id='changeData'></div>
<Child getData={this.getData}/>
</p>
</div>
}
}
子组件:Child.js
import React, {Component} from 'react'
export default class Child extends Component{
state = {
data:[1,2,3]
}
render() {
const {data} = this.state
return (
<div>
<button onClick={()=> {this.props.getData(data)}}>子组件</button>
</div>
)
}
}
八. 父组件向子组件传值,子组件操作修改父组件
在表格的每一行增加删除按钮, Table.js和Table1.js分别是两种方法传值,删除方法也不同
Table.js
(Table.js的方法不可取,如果不设置state数据的话,删除数据,后台打印信息中可删除,但页面不会变化。在此仅做对比参考)
import React, {Component} from 'react'
// 方法三<TableHead Head={Head}/>
const TableHead = (props) => {
console.log('Head',props)
const myHead = props.Head.map((item,index) => {
return <th key={index}>{item.header}</th>
})
return (
<thead>
<tr>{myHead}</tr>
</thead>
)
}
const TableBody = (props) => {
console.log('Body',props)
const myBody = props.Body.map((item1,index1) => {
return <tr key={index1}>
<td>{item1.name}</td>
<td>{item1.job}</td>
<td>
<button onClick={()=>props.removeTr(props.Body,index1)}>Delete</button>
</td>
</tr>
})
return (
<tbody>{myBody}</tbody>
)
}
class Table extends Component{
render() {
// 拿取传递过来的数据
const {Head,Body, removeTr} = this.props
console.log('total', this.props)
return (
<table>
{/*二. 调用组件,相互嵌套*/}
<TableHead Head={Head}/>
<TableBody Body={Body} removeTr={removeTr}/>
</table>
)
}
}
export default Table
Table1.js
import React, {Component} from 'react'
const TableHead = (props) => {
const myHead = props.Head.map((item,index) => {
return <th key={index}>{item.header}</th>
})
return (
<thead>
<tr>{myHead}</tr>
</thead>
)
}
const TableBody = (props) => {
console.log('body1', props)
const myBody = props.Body.map((item,index) => {
return <tr key={index}>
<td>{item.name}</td>
<td>{item.job}</td>
<td>
<button onClick={()=>props.removeTr1(index)}>Delete</button>
</td>
</tr>
})
return (
<tbody>{myBody}</tbody>
)
}
class Table1 extends Component{
render() {
const {state,removeTr1} = this.props
console.log('state', {state})
return (
<table>
<TableHead Head={state.Head}/>
<TableBody Body={state.Body} removeTr1={removeTr1}/>
</table>
)
}
}
export default Table1
home.js当将this.state放在constructor里时,点击删除按钮,页面中可实际删除。(编译时也不会弹警告)或者将state放在Render外,
1。放在constructor里
import React from 'react';
import {Link} from "react-router-dom";
import Table from "./Table";
import Table1 from "./Table1";
export default class home extends React.Component{
constructor(props) {
super(props);
this.state = {
Head: [
{header: 'Name'},
{header: 'Job1'}
],
Body: [
{
name: '李狗蛋',
job: '程序猿',
},
{
name: '王翠花',
job: '攻城狮',
},
{
name: '二狗子',
job: '加班狗',
}
]
}
}
removeTr1 = (index)=> {
const {Body} = this.state
this.setState({
Body: Body.filter((item,ind) => {
console.log('index', index+","+ind)
console.log('removeTr1', Body.filter((item,index1)=>index1 !== index))
return ind !== index
}
)
// Body: Body.filter((item,ind) => ind !== index)
})
}
render(){
return <div>
<h2>这是首页内容......</h2>
<p>
<div style={{border: '1px solid #fff000',padding: '10px'}}>
state 方式传值
<Table1 state={this.state} removeTr1={this.removeTr1}/>
</div>
</p>
</div>
}
}
2。放在render外面
import React from 'react';
import {Link} from "react-router-dom";
import Table1 from "./Table1";
export default class home extends React.Component{
removeTr1 = (index)=> {
const {Body} = this.state
this.setState({
Body: Body.filter((item,ind) => {
console.log('index', index+","+ind)
console.log('removeTr1', Body.filter((item,index1)=>index1 !== index))
return ind !== index
}
)
// Body: Body.filter((item,ind) => ind !== index)
})
}
state = {
Head: [
{header: 'Name'},
{header: 'Job1'}
],
Body: [
{
name: '李狗蛋',
job: '程序猿',
},
{
name: '王翠花',
job: '攻城狮',
},
{
name: '二狗子',
job: '加班狗',
}
]
}
render(){
return <div>
<h2>这是首页内容......</h2>
<p>
<div style={{border: '1px solid #fff000',padding: '10px'}}>
state 方式传值
<Table1 state={this.state} removeTr1={this.removeTr1}/>
</div>
</p>
</div>
}
}
九. Form表单,增加数据
import React, { Component } from 'react'
export default class Form extends Component{
constructor(props) {
super(props);
//初始化input的value值
this.initValue= {
name: '',
job: ''
}
// 将初始化的值赋值给state
this.state = this.initValue
}
//input的标签内容改变时执行
handleChange = (e) => {
this.setState({
[e.target.name]: e.target.value,
})
}
//点击提交按钮时
submitForm = () => {
//这个方法是父组件那边传过来的,需要把用户输入的数据传过去
this.props.handleSubmit(this.state)
console.log('Form submitForm', this.state)
console.log('Form submitForm', this.props)
//重置input的value值
this.setState(this.initValue)
//在页面显示新增的数据
document.getElementById("addInput").textContent=JSON.stringify(this.state)
}
render() {
const {name,job} = this.state
return (
<form>
<label>
Name:
</label>
<input type="text" value={name} name="name" onChange={this.handleChange}/><br/>
<label>
Job:
</label>
<input type="text" value={job} name="job" onChange={this.handleChange}/>
<div id="addInput"></div>
<input type="button" value="新增" onClick={this.submitForm}/>
</form>
);
}
}
父组件home.js
handleSubmit = (valObj) => {
// 通过解构的方式,把传过来的数据添加到Body数组里,
console.log('home handleSubmit', valObj)
console.log('home handleSubmit', this.state.Body)
this.setState({
// Body: this.state.Body.push(valObj)
Body: [...this.state.Body, valObj]
})
console.log('home handleSubmit2', this.state.Body)
}
十. 完整代码:
将原来放在App.js中的代码放在了home.js中,完成了父子组件的传值和调用
学习要点:
1. Table1.js:父组件向子组件传值,显示列表数据,删除数据并刷新页面;组件的嵌套;
2. Form.js: 提交表单,增加数据,向父组件传值并刷新页面;
3. Child.js: 子组件向父组件传值
4. Button.js: 生命周期的调用
5. App.js: 展示了两种路由
home.js
import React from 'react';
import {Link} from "react-router-dom";
import Child from "./Child";
import Table from "./Table";
import Table1 from "./Table1";
import Button from "./Button";
import Form from "./Form";
export default class home extends React.Component{
getData=(data)=>{
console.log(data)
document.getElementById("changeData").textContent = data
}
removeTr = (Body, index)=> {
// const {Body} = this.Body
this.setState({
Body: Body.filter((item,ind) => {
console.log('index', index+","+ind)
console.log('dfsdf', Body.filter((item,index1)=>index1 !== index))
return ind !== index
}
)
// Body: Body.filter((item,ind) => ind !== index)
})
}
removeTr1 = (index)=> {
const {Body} = this.state
this.setState({
Body: Body.filter((item,ind) => {
console.log('index', index+","+ind)
console.log('removeTr1', Body.filter((item,index1)=>index1 !== index))
return ind !== index
}
)
// Body: Body.filter((item,ind) => ind !== index)
})
}
handleSubmit = (valObj) => {
// 通过解构的方式,把传过来的数据添加到Body数组里,
console.log('home handleSubmit', valObj)
console.log('home handleSubmit', this.state.Body)
this.setState({
// Body: this.state.Body.push(valObj)
Body: [...this.state.Body, valObj]
})
console.log('home handleSubmit2', this.state.Body)
}
state = {
Head: [
{header: 'Name'},
{header: 'Job1'}
],
Body: [
{
name: '李狗蛋',
job: '程序猿',
},
{
name: '王翠花',
job: '攻城狮',
},
{
name: '二狗子',
job: '加班狗',
}
]
}
render(){
const Head = [
{header: 'Name'},
{header: 'Job1'}
]
const Body = [
{
name: '李狗蛋',
job: '程序猿',
},
{
name: '王翠花',
job: '攻城狮',
},
{
name: '二狗子',
job: '加班狗',
}
]
return <div>
<h2>这是首页内容......</h2>
<p>
<div style={{border: '1px solid #fff000',padding: '10px'}}>
各自传值
<Table Head={Head} Body={Body} removeTr={this.removeTr}/>
</div>
{/*<Table Head={Head} Body={Body}/>*/}
<div style={{border: '1px solid #fff000',padding: '10px'}}>
state 方式传值
<Table1 state={this.state} removeTr1={this.removeTr1}/>
</div>
</p>
<p><Link to="/lifecycle">React生命周期</Link></p>
<p><Button/></p>
<p><Link to="/help/">帮助</Link></p>
<p>
父组件<br/>
点击子组件,向父组件传值
<div id='changeData'></div>
<Child getData={this.getData}/>
</p>
<p>
<Form handleSubmit={this.handleSubmit}/>
</p>
</div>
}
}
Table1.js : 父组件向子组件传值,显示表格信息,删除数据
import React, {Component} from 'react'
const TableHead = (props) => {
const myHead = props.Head.map((item,index) => {
return <th key={index}>{item.header}</th>
})
return (
<thead>
<tr>{myHead}</tr>
</thead>
)
}
const TableBody = (props) => {
console.log('Table1 body1', props)
const myBody = props.Body.map((item,index) => {
return <tr key={index}>
<td>{item.name}</td>
<td>{item.job}</td>
<td>
<button onClick={()=>props.removeTr1(index)}>Delete</button>
</td>
</tr>
})
return (
<tbody>{myBody}</tbody>
)
}
class Table1 extends Component{
render() {
const {state,removeTr1} = this.props
console.log('Table1 state', {state})
return (
<table>
<TableHead Head={state.Head}/>
<TableBody Body={state.Body} removeTr1={removeTr1}/>
</table>
)
}
}
export default Table1
Form.js: 增加数据,向父组件传值
import React, { Component } from 'react'
export default class Form extends Component{
constructor(props) {
super(props);
//初始化input的value值
this.initValue= {
name: '',
job: ''
}
// 将初始化的值赋值给state
this.state = this.initValue
}
//input的标签内容改变时执行
handleChange = (e) => {
this.setState({
[e.target.name]: e.target.value,
})
}
//点击提交按钮时
submitForm = () => {
//这个方法是父组件那边传过来的,需要把用户输入的数据传过去
this.props.handleSubmit(this.state)
console.log('Form submitForm', this.state)
console.log('Form submitForm', this.props)
//重置input的value值
this.setState(this.initValue)
//在页面显示新增的数据
document.getElementById("addInput").textContent=JSON.stringify(this.state)
}
render() {
const {name,job} = this.state
return (
<form>
<label>
Name:
</label>
<input type="text" value={name} name="name" onChange={this.handleChange}/><br/>
<label>
Job:
</label>
<input type="text" value={job} name="job" onChange={this.handleChange}/>
<div id="addInput"></div>
<input type="button" value="新增" onClick={this.submitForm}/>
</form>
);
}
}
Child.js:子组件向父组件传值
import React, {Component} from 'react'
export default class Child extends Component{
state = {
data:[1,2,3]
}
render() {
const {data} = this.state
return (
<div>
<button onClick={()=> {this.props.getData(data)}}>子组件</button>
</div>
)
}
}
App.js:
import './App.css';
import {
// Switch,
HashRouter as Router,
// BrowserRouter as Router,
Route,
} from 'react-router-dom';
import help from "./page/help";
import home from "./page/home";
import Button from "./page/Button"
function App() {
return (
// <Router>
// {/*只显示某一页的内容 http://localhost:3000/#/或http://localhost:3000/#/help*/}
// <Switch>
// <Route path="/" exact component={home} />
// <Route path="/help" component={help}/>
// </Switch>
// </Router>
// 显示导航栏并在下方显示选中的页面内容
<Router>
<div>
<Route exact path="/" component={home}/>
<Route path="/help" component={help}/>
<Route path="/lifecycle" component={Button}/>
{/*<Table Head={Head} Body={Body}/>*/}
</div>
</Router>
);
}
export default App;
补充:Button.js生命周期的调用:
import React, {Component} from 'react'
export default class Button extends Component{
constructor(props) {
super(props);
this.state = {data: 0};
this.setNewNumber = this.setNewNumber.bind(this);
}
setNewNumber() {
this.setState({data: this.state.data +1})
}
render() {
return (
<div style={{border: '1px solid #fff000',padding: '10px'}}>
<button onClick={this.setNewNumber}>INCREMENT</button>
<Content myNumber={this.state.data}></Content>
</div>
)
}
}
class Content extends Component{
// 在渲染前调用,在客户端也在服务端。
componentWillMount() {
console.log('Component Will mount')
}
// 在第一次渲染后调用,只在客户端。之后组件已经生成了对应的DOM结构,可以通过this.getDOMNode()来进行访问。 如果你想和其他JavaScript框架一起使用,可以在这个方法中调用setTimeout, setInterval或者发送AJAX请求等操作(防止异步操作阻塞UI)。
componentDidMount() {
console.log('Component Did mount')
}
// 在组件接收到一个新的 prop (更新后)时被调用。这个方法在初始化render时不会被调用。
componentWillReceiveProps(nextProps, nextContext) {
console.log('Component Will Receive props', nextProps)
}
// 返回一个布尔值。在组件接收到新的props或者state时被调用。在初始化时或者使用forceUpdate时不被调用
shouldComponentUpdate(nextProps, nextState, nextContext) {
console.log('shouldComponentUpdate', nextProps)
console.log('shouldComponentUpdate', nextState)
return true;
}
// 在组件接收到新的props或者state但还没有render时被调用。在初始化时不会被调用
componentWillUpdate(nextProps, nextState, nextContext) {
console.log('component Will update', nextProps)
console.log('component Will update', nextState)
}
// 在组件完成更新后立即调用。在初始化时不会被调用。
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('component Did update', prevProps)
console.log('component Did update', prevState)
}
// 在组件从 DOM 中移除之前立刻被调用。
componentWillUnmount() {
console.log('component Will unmount')
}
render() {
return (
<div>
<h3>{this.props.myNumber}</h3>
</div>
)
}
}