- 使用以下命令创建一个工程
npx create-react-app my-react
cd my-app
npm start
-
工程目录
工程目录.jpg -
基本配置如下
// package.json
"dependencies": {
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.5.0",
"@testing-library/user-event": "^7.2.1",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-scripts": "3.4.1"
},
- 根目录下的
index.js
中值得关注的点:
// 逻辑层
import React from 'react';
// 渲染层
import ReactDOM from 'react-dom';
// 默认使用严格模式,这里绑定根节点的语法可以参考vue的语法去理解
ReactDOM.render(
<React.StrictMode>
<App />,
</React.StrictMode>,
document.getElementById('root')
);
-
App.js
中我们可以看到,有个类组件App
,继承于React.Component
,组件中实现了一个很重要的函数render
,render
中输出的内容是JSX写法,下面来具体了解一下 JSX -
JSX基本语法
// App.js
import React from 'react';
import logo from './logo.svg';
import './App.css'
function formatName (user) {
return `${user.firstName},${user.lastName}`
}
class App extends React.Component {
render () {
const NAME = 'jerry'
const USER = {firstName: 'tom', lastName: 'jerry'}
let jsx = <p>hello,react</p>
return (
<div className="App">
{/* 合法的js表达式 */}
{/* for循环、条件语句都是不可以的 */}
<h1>{NAME}</h1>
<h2>{formatName(USER)}</h2>
{/* 属性 */}
{/* 这里的style是动态语法,style={},里面的值是个对象*/}
<img src={logo} style={{width: '200px'}} alt="" />
{/* jsx也是表达式 */}
{jsx}
</div>
);
}
}
export default App;
-
function
组件、class
类组件- 注意2种不同组件实现方式获取传值的区别,也就是获取
props
的区别,同时props
只支持单项数据流,所以获取来的值只读
- 注意2种不同组件实现方式获取传值的区别,也就是获取
// src/components/CompType.js
import React from 'react';
// 函数类型组件
export function Welcome1 (props) {
return (
<div>
Welcome1, {props.name}
</div>
)
}
// 类组件
export class Welcome2 extends React.Component{
render(){
return <div>Welcome2, {this.props.name}</div>
}
}
// App.js 添加以下内容
import {Welcome1, Welcome2} from './components/CompType'
class App extends React.Component {
render () {
return (
<div className="App">
{/* 组件使用 */}
<Welcome1 name="name1"></Welcome1>
<Welcome2 name="name2"></Welcome2>
</div>
)
}
}
-
state
与setState
-
state
可以理解为Vue中组件的data
属性,而setState
是React要求的修改state
必须调用该方法修改
-
// src/components/Clock.js
import React from 'react';
export class Clock extends React.Component{
// 组件状态
state = {
date: new Date()
}
// 组件挂载之后
componentDidMount () {
this.timer = setInterval(() => {
this.setState({
date: new Date()
})
}, 1000)
}
// 组件即将销毁
compoentWillUnmount () {
clearInterval(this.timer);
}
render(){
return (
<div>
{/* 输出当前组件某个状态的值 */}
{this.state.date.toLocaleTimeString()}
</div>
)
}
}
-
setState
使用的注意事项-
setState
本身不是异步的,所以想要依赖上一步的值做变化建议使用函数式写法,实现异步,通过回调函数操作数据更方便
-
// src/components/StateTest.js
import React from 'react';
export class StateTest extends React.Component{
state = {
count: 1
}
// 组件挂载之后
componentDidMount () {
// 1. 直接修改state数据无效,必须使用setState
// this.state.count += 10
// 2. setState批量执行
// 写3遍同样的对count的操作,setState只会执行一次,setState会进行队列排列,对方法进行整合,相同的操作会被合并
// this.setState(obj, cb)
// 这里执行完毕显示的值为11
this.setState({count: this.state.count + 10});
this.setState({count: this.state.count + 10});
this.setState({count: this.state.count + 10});
// 3.如果前后的值是有关联的,比如后面的值依赖于之前的值,可以用函数形式写,prevState可以获取到已经被操作之后的值
// this.setState(fn, cb)
// 这里执行完毕显示的值为41
this.setState(prevState => ({
count: prevState.count + 10
}))
this.setState(prevState => ({
count: prevState.count + 10
}))
this.setState(prevState => ({
count: prevState.count + 10
}))
}
render(){
return (
<div>
{this.state.count}
</div>
)
}
}
以下通过一个购物车组件展示组件的条件渲染、列表渲染、输入框操作、事件操作、组件间通信
- 条件渲染
// src/components/CartSample.js
import React from 'react';
export class CartSample extends React.Component{
// 状态state初始化一般放在构造器中
constructor (props) {
super(props);
this.state = {}
render(){
// 判断父组件是否传入title属性
let title = this.props.title ? <h5>{this.props.title}</h5> : null
return (
<div>
{/* 条件渲染 */}
{/* 写法1 */}
{title}
{/* 写法2 当this.props.title为空就不再继续执行 */}
{this.props.title && <h5>{this.props.title}</h5>}
</div>
)
}
}
// App.js 引入CartSample组件
<CartSample title="this is cartsample title"></CartSample>
- 列表渲染
// src/components/CartSample.js
import React from 'react';
export class CartSample extends React.Component{
// 状态state初始化一般放在构造器中
constructor (props) {
super(props);
this.state = {
goods: [
{id: 1, name: 'web'},
{id: 2, name: 'ios'},
{id: 3, name: 'pc'},
{id: 4, name: 'mobile'},
]
}
render(){
// 判断父组件是否传入title属性
let title = this.props.title ? <h5>{this.props.title}</h5> : null
return (
<div>
{/* 条件渲染 */}
{/* 写法1 */}
{title}
{/* 写法2 当this.props.title为空就不再继续执行 */}
{this.props.title && <h5>{this.props.title}</h5>}
{/* 列表渲染 */}
<ul>
{this.state.goods.map(good => (
<li key={good.id}>
{good.name}
</li>
))}
</ul>
</div>
)
}
}
- 输入框操作:监听输入框的操作
// src/components/CartSample.js
import React from 'react';
export class CartSample extends React.Component{
// 状态state初始化一般放在构造器中
constructor (props) {
super(props);
this.state = {
goods: [
{id: 1, name: 'web'},
{id: 2, name: 'ios'},
{id: 3, name: 'pc'},
{id: 4, name: 'mobile'},
],
// 新增一个状态用于存储输入框的值
text: '',
}
// 这里一定要写成箭头函数,因为这里的方法在调用时会影响this指向
// 也就是textChange方法如果不用箭头函数表示,那么在调用时this默认指向调用者,也就是input,这样就拿不到组件状态state中的数据了
textChange = (evt) => {
this.setState({text: evt.target.value})
}
render(){
// 判断父组件是否传入title属性
let title = this.props.title ? <h5>{this.props.title}</h5> : null
return (
<div>
{/* 条件渲染 */}
{/* 写法1 */}
{title}
{/* 写法2 当this.props.title为空就不再继续执行 */}
{this.props.title && <h5>{this.props.title}</h5>}
{/* 输入框操作 */}
<div>
{/* 这里注意赋值给绑定事件的方法一定不能写成this.textChange(),这样就表示立即调用了,其实这里需要的是个方法体 */}
{/* react没有数据双向绑定的概念,因此一个输入框必须要设置好2点,值得绑定和值变化时的处理方法 */}
<input type="text" value={this.state.text} onChange={this.textChange}/>
</div>
{/* 列表渲染 */}
<ul>
{this.state.goods.map(good => (
<li key={good.id}>
{good.name}
</li>
))}
</ul>
</div>
)
}
}
- 事件操作 - 新增一个“添加”按钮,功能是将输入框中输入法人课程名称添加至课程列表
// src/components/CartSample.js
import React from 'react';
export class CartSample extends React.Component{
// 状态state初始化一般放在构造器中
constructor (props) {
super(props);
this.state = {
goods: [
{id: 1, name: 'web'},
{id: 2, name: 'ios'},
{id: 3, name: 'pc'},
{id: 4, name: 'mobile'},
],
// 新增一个状态用于存储输入框的值
text: '',
}
// 这里一定要写成箭头函数,因为这里的方法在调用时会影响this指向
// 也就是textChange方法如果不用箭头函数表示,那么在调用时this默认指向调用者,也就是input,这样就拿不到组件状态state中的数据了
textChange = (evt) => {
this.setState({text: evt.target.value})
}
// 添加至商品列表
addGood = () => {
this.setState(prevState => {
// 返回一个全新的要更新的数组,而不是在原数组上操作数据
return {
goods: [...this.state.goods, {id: prevState.goods.length + 1, name: prevState.text}]
}
})
}
render(){
// 判断父组件是否传入title属性
let title = this.props.title ? <h5>{this.props.title}</h5> : null
return (
<div>
{/* 条件渲染 */}
{/* 写法1 */}
{title}
{/* 写法2 当this.props.title为空就不再继续执行 */}
{this.props.title && <h5>{this.props.title}</h5>}
{/* 输入框操作 */}
<div>
{/* 这里注意赋值给绑定事件的方法一定不能写成this.textChange(),这样就表示立即调用了,其实这里需要的是个方法体 */}
{/* react没有数据双向绑定的概念,因此一个输入框必须要设置好2点,值得绑定和值变化时的处理方法 */}
<input type="text" value={this.state.text} onChange={this.textChange}/>
<button onClick={this.addGood}>添加</button>
</div>
{/* 列表渲染 */}
<ul>
{this.state.goods.map(good => (
<li key={good.id}>
{good.name}
</li>
))}
</ul>
</div>
)
}
}
- 组件间通信
- 新增一个购物车列表组件,只做数据展示,不做逻辑处理
- 原先的课程列表,给每一项新增一个“添加至购物车”的按钮
- 购物车列表可以自增、自减当前已有的内容
// src/components/CartSample.js
import React from 'react';
export class CartSample extends React.Component{
// 状态state初始化一般放在构造器中
constructor (props) {
super(props);
this.state = {
goods: [
{id: 1, name: 'web'},
{id: 2, name: 'ios'},
{id: 3, name: 'pc'},
{id: 4, name: 'mobile'},
],
// 新增一个状态用于存储输入框的值
text: '',
// 用于保存购物车列表数据
cart: []
}
// 这里建议写成箭头函数,因为这里的方法在调用时会影响this指向
// 也就是textChange方法如果不用箭头函数表示,那么在调用时this默认指向调用者,也就是input,这样就拿不到组件状态state中的数据了
// 如果不写成箭头函数,写成 textChange () {},那么在组件state中可以使用bind等方法重新设置函数的this指向
textChange = (evt) => {
this.setState({text: evt.target.value})
}
// 添加至商品列表
addGood = () => {
this.setState(prevState => {
// 返回一个全新的要更新的数组,而不是在原数组上操作数据
return {
goods: [...this.state.goods, {id: prevState.goods.length + 1, name: prevState.text}]
}
})
}
// 添加至购物车
addCart = good => {
// 创建新购物车
const newCart = [...this.state.cart]
// 查找当前选中项是否已存在在购物车
const idx = newCart.findIndex(c => c.id === good.id)
const item = newCart[idx]
if(item) {
// 当前选中项已存在在购物车,购物车中的当前项数量+1
newCart.splice(idx, 1, {...item, count: item.count + 1})
}else {
// 购物车新增一项
newCart.push({...good, count: 1})
}
this.setState({cart: newCart})
}
// 购物车自减
minus = good => {
const newCart = [...this.state.cart]
const idx = newCart.findIndex(c => c.id === good.id)
const item = newCart[idx]
newCart.splice(idx, 1, {...item, count: item.count - 1})
this.setState({cart: newCart})
}
// 购物车自增
add = good => {
const newCart = [...this.state.cart]
const idx = newCart.findIndex(c => c.id === good.id)
const item = newCart[idx]
newCart.splice(idx, 1, {...item, count: item.count + 1})
this.setState({cart: newCart})
}
render(){
// 判断父组件是否传入title属性
let title = this.props.title ? <h5>{this.props.title}</h5> : null
return (
<div>
{/* 条件渲染 */}
{/* 写法1 */}
{title}
{/* 写法2 当this.props.title为空就不再继续执行 */}
{this.props.title && <h5>{this.props.title}</h5>}
{/* 输入框操作 */}
<div>
{/* 这里注意赋值给绑定事件的方法一定不能写成this.textChange(),这样就表示立即调用了,其实这里需要的是个方法体 */}
{/* react没有数据双向绑定的概念,因此一个输入框必须要设置好2点,值得绑定和值变化时的处理方法 */}
<input type="text" value={this.state.text} onChange={this.textChange}/>
<button onClick={this.addGood}>添加</button>
</div>
{/* 列表渲染 */}
<ul>
{this.state.goods.map(good => (
<li key={good.id}>
{good.name}
{/* 事件绑定:不需要传参时,像课程列表的添加事件,只需要传一个函数体就可以了 */}
{/* 事件绑定:需要传参时,将绑定事件写成一个箭头函数,在函数中执行绑定的方法并传参 */}
<button onClick={() => this.addCart(good)}>添加购物车</button>
</li>
))}
</ul>
{/* 购物车 */}
{/* 和Vue不同的是,react中子组件尽可能的只做数据展示,不做数据管理及事件操作,有需要事件操作的通知父组件进行操作 */}
{/* 子组件需要执行的方法函数,在父组件中以属性的形式传过去 */}
<Cart data={this.state.cart} minus={this.minus} add={this.add}></Cart>
</div>
)
}
}
// 购物车组件
function Cart ({data, minus, add}) {
return (
<div>
<table>
<tbody>
{data.map(d => (
<tr key={d.id}>
<td>{d.name}</td>
<td>
<button onClick={() => minus(d)}>-</button>
{d.count}
<button onClick={() => add(d)}>+</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
)
}
网友评论