react的特点
react 和 vue 的区别
- 首先,这两个框架,都是以组件化的思想进行开发的!
- 从开发团队上进行对比:
- React的开发团队,是Facebook官方大牛团队,团队技术实力雄厚;
- Vue:第一个版本,主要是作者尤雨溪进行维护。
- 从社区方面进行对比:
- React社区早,解决方案多
- Vue社区晚一点,解决方案相对少一点
- 从移动App开发方面:
- 使用React这门技术,可以分分钟转到ReactNative的开发中
- VUE 这门技术,也提供了无缝的转移到移动端开发的体验,通过weex可以使用VUE的语法,进行移动端APP开发
脚手架
快速开始
npx create-react-app my-app
cd my-app
npm run start
JSX
JSX嵌套元素
组件中,必须返回一个根元素
如 下列语法是错误的 !
import React, { Component } from 'react'
export default class App extends Component {
render() {
return (
<div>
Hello World!
</div>
<div>
Hello World!
</div>
)
}
}
假如要想在根目录下直接渲染两个标签,可以使用
Fragment
标签,类似vue
中的template
引入Fragment组件
import React, { Fragment } from 'react';
包裹标签
import React, { Fragment } from 'react';
import ReactDOM from 'react-dom';
const App = ()=> {
return (
<Fragment>
<h1>我就是jsx</h1>
<h1>我就是jsx</h1>
</Fragment>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
JSX表达式
1.普通渲染
<h1>我就是jsx</h1>
2.数学表达式
<h1>{1 + 1}</h1>
3.字符串
<h1>{'hello world'}</h1>
4.bool类型-无法渲染
<h1>{isBoy}</h1>
5.使用变量
<h1>{msg}</h1>
6.三目运算符
<h1>{isBoy ? "男生" : "女生"}</h1>
7.调用方法
const format = (msg) => {
return '---' + msg + '---';
}
<h1>{format(msg)}</h1>
8.使用对象
const lamian = {
name: "拉面"
};
<h1>{lamian.name}</h1>
代码
import React, { Fragment } from 'react';
import ReactDOM from 'react-dom';
const msg = "你们好呀";
const isBoy = false;
const format = (msg) => {
return '---' + msg + '---';
}
const lamian = {
name: "拉面"
};
const App = () => {
return (
<Fragment>
<h1>我就是jsx</h1>
{/* 数字 */}
<h1>{1 + 1}</h1>
{/* 字符串 */}
<h1>{'hello world'}</h1>
{/* bool类型 */}
<h1>{isBoy}</h1>
{/* 使用变量 */}
<h1>{msg}</h1>
{/* 3目运算符 */}
<h1>{isBoy ? "男生" : "女生"}</h1>
{/* 调用方法 */}
<h1>{format(msg)}</h1>
{/* 使用对象 */}
<h1>{lamian.name}</h1>
</Fragment>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
JSX嵌套语法与循环
import React from 'react';
import ReactDOM from 'react-dom';
const msg = "nice 天气";
const App = () => {
return (
<div >
{
<div>
<h1>怎么啦 {msg} </h1>
<h1>怎么啦 {msg} </h1>
</div>
}
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
加强版本
import React from 'react';
import ReactDOM from 'react-dom';
const list = ['🍎', '🍌', '🍐', '🍉'];
const App = () => {
return (
<div >
{
<div>
{
list.map(function (v) {
return (
<h1 key={v}>{v}</h1>
)
})
}
</div>
}
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
终极版
import React from 'react';
import ReactDOM from 'react-dom';
const list = ['苹果', '香蕉', '雪梨', '西瓜'];
const App = () => {
return (
<div >
{
<div>
{
list.map(v => <h1 key={v}>{v}</h1>)
}
</div>
}
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
JSX注释
{
// 这里是单行注释
}
{
/*
这里是多行注释
这里是多行注释
这里是多行注释
这里是多行注释
*/
}
JSX标签属性
jsx
标签上可以设置绝大部分和以前html
标签一样的属性,如checked
、图片的src
需要注意几个点
-
html
的class
属性改为className
<div className="redCls">👍💯☁️</div>
-
html
中label
标签的for
属性改为htmlFor
<label htmlFor="inp"> 点我点我 <input id="inp" type="text" /> </label>
-
标签中的自定义属性使用
data
<div data-index={'hello'} >自定义属性</div>
-
渲染
html
字符串 使用dangerouslySetInnerHTML
属性<li dangerouslySetInnerHTML={{__html:"<i>来啊呀</i>"}}></li>
-
bool
类型的值 可以这样用<input type="checkbox" checked={true} />
-
当属性太多了,可以使用
...
扩展运算符const props={ className:"redCls", "data-index":5 } <div {...props}>展开属性</div>
JSX的行内样式
JSX可以像传统的
HTML
标签一样添加行内样式**,不同的是,要通过对象的方式来实现。并且属性名是以驼峰命名。**
import React from 'react';
import ReactDOM from 'react-dom';
import "./index.css";
const App = () => {
return (
<div style={{ color: 'yellow', fontSize: "150px", "backgroundColor": 'red' }} > 颜色真不错</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
JSX创建组件的本质 了解
JSX俗称 语法糖
,提供了一种更好使用的语法让我们使用,其本质是调用 React.createElement
实现的
React.createElement
,接收3个参数
- 标签名 如
"div"
- 标签上的属性,如
{className:"redCls"}
- 文本内容或者 另一个
React.createElement
对象或者React.createElement
数组
import React from 'react';
import ReactDOM from 'react-dom';
import "./index.css";
const parentProps = {
className: "redCls",
"data-index": 1000
}
const App = () => {
return React.createElement(
"div",
parentProps,
[
React.createElement(
"span",
null,
"不错啊呀"
),
React.createElement(
"span",
null,
"真好呀"
)
]
)
}
ReactDOM.render(<App />, document.getElementById('root'))
1.组件的创建
在react中,组件分为两种,类组件 和 函数式组件
1.简单功能 使用 函数式组件
2.复杂功能 使用 类组件
3.组件名都必须大写
函数式组件
import React from 'react'
export default function App() {
return (
<div>
</div>
)
}
类组件
使用es6创建class的方式来实现一个组件类
1.首字母要大写
2.要继承 React中的Component类
3.必须实现render函数,函数内返回标签
4.组件有自己的state和生命周期
import React,{Component} from 'react';
import ReactDOM from 'react-dom';
import "./index.css";
class App extends Component {
render() {
return (
<div>
嘿嘿嘿
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'))
小结
- 函数式组件性能更高,因为没有生命周期
- 函数式组件更方便进行测试
- 能不用类组件就不用类组件
- 当要使用
state
时,就要使用类组件
2.状态和属性
在
React
中,状态和属性都可以实现数据动态化
状态 state
在react中,组件内部的数据是通过
state
来实现和管理
可以理解为
state
就是Vue
中的data
函数式组件没有自己的state
1.state的声明和使用
在类组件中,state的声明分为两种方式
1-1.类属性的方式声明
class Person extends Component {
// 1 声明 state
state = {
date: "2009",
msg: "天啊天啊"
}
render() {
return (
<div>
{/* 2 使用state */}
<h1>{this.state.date}</h1>
<h2>{this.state.msg}</h2>
</div>
)
}
}
1-2.构造函数中声明
class Person extends Component {
// 1 构造函数中 声明 state
constructor() {
// 1.1 必须在this之前调用super()方法
super();
this.state = {
date: "2009",
msg: "天啊天啊"
}
}
render() {
return (
<div>
{/* 2 使用state */}
<h1>{this.state.date}</h1>
<h2>{this.state.msg}</h2>
</div>
)
}
}
2.state的赋值
state
的赋值方式通过this.setState
方法 来实现需要注意的是, 不能 使用
this.state.date= 200
直接修改
class Person extends Component {
state = {
date: 2008
}
// 2 事件的声明 要使用箭头函数
handleClick = () => {
// 3 获取state中的日期
let { date } = this.state;
// 4 修改state中的日期
this.setState({
date: date + 1
});
}
render() {
return (
// 1 绑定事件 事件名必须驼峰命名
<div onClick={this.handleClick}>
<h1>{this.state.date}</h1>
</div>
)
}
}
3.state的赋值是异步的
react为了优化性能,将state的赋值代码 改成异步的方式,可以避免反复的设置state而引发的性能损耗问题。
看看下面打印的值
class Person extends Component {
state = {
date: 2010
}
handleClick = () => {
let { date } = this.state;
// 1 修改state中的日期 增加 2000
this.setState({
date: date + 2000
});
// 2 此时这个date还是2010而不是4010
console.log(this.state.date);
}
render() {
return (
<div onClick={this.handleClick}>
<h1>{this.state.date}</h1>
</div>
)
}
}
有时候,我们希望在一设置值的时候,就希望马上得到最新的state的值,那么可以将代码改为下列的写法
给
setState
添加一个回调函数,回调中可以获取到修改后的state
的值
class Person extends Component {
state = {
date: 2008
}
handleClick = () => {
let { date } = this.state;
// 添加一个回调函数
this.setState({
date: date + 3000
}, () => {
// date的值为 5008
console.log(this.state.date);
});
}
render() {
return (
<div onClick={this.handleClick}>
<h1>{this.state.date}</h1>
</div>
)
}
}
有时候,setState还可以接收一个函数,函数内可以实时获取state中的值,不存在延迟
this.setState(preState => {
console.log("上一次的state", preState.date);
return {
date: preState.date + 1000
}
})
属性 props
props意思是属性,一般存在 父子组件中。用于 父向子传递数据
子组件不能修改接收到的props值
1.声明一个类组件 HomeTop 父组件的数据通过 this.props
来获取
class HomeTop extends Component {
render() {
return (
<h1>屋顶的颜色是 {this.props.acolor} 尺寸 {this.props.asize}</h1>
)
}
}
2.声明一个函数式组件HomeFooter
,父组件传递的数据 需要在函数的形参props
上接收
const HomeFooter = (props) => {
return <h1>屋底的颜色是 {props.bcolor} 尺寸 {props.bsize}</h1>
}
3.声明父组件,并在标签上通过属性的方式进行传递数据
class Home extends Component {
state = {
color: "blue",
size: 100
}
render() {
return (
<div>
<HomeTop acolor={this.state.color} asize={this.state.size} ></HomeTop>
<HomeFooter bcolor={this.state.color} bsize={this.state.size} ></HomeFooter>
</div>
)
}
}
完整代码
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class HomeTop extends Component {
render() {
return (
<h1>屋顶的颜色是 {this.props.acolor} 尺寸 {this.props.asize}</h1>
)
}
}
const HomeFooter = (props) => {
return <h1>屋底的颜色是 {props.bcolor} 尺寸 {props.bsize}</h1>
}
class Home extends Component {
state = {
color: "blue",
size: 100
}
render() {
return (
<div>
<HomeTop acolor={this.state.color} asize={this.state.size} ></HomeTop>
<HomeFooter bcolor={this.state.color} bsize={this.state.size} ></HomeFooter>
</div>
)
}
}
ReactDOM.render(<Home />, document.getElementById('root'))
类组件构造函数中的props
当想要在类组件的构造函数中,获取到props时,需要如下使用
constructor(props) {
super(props);
console.log(props);
}
props默认值
当父元素没有传递props属性时,子组件可以指定一个默认props属性值来使用。
通过
组件名.defaultProps
来指定
import React from 'react';
import ReactDOM from 'react-dom';
let HomeNav = (props) => {
return <h1> 导航为 {props.color} </h1>
// 或者
static defaultProps = {
color: "yellow"
}
}
// 指定一个默认属性
HomeNav.defaultProps = {
color: "yellow"
}
class Home extends Component {
state = {
color: "blue"
}
render() {
return (
<div>
<HomeNav></HomeNav>
</div>
)
}
}
ReactDOM.render(<Home />, document.getElementById('root'))
props类型校验
在一些要求代码更为严格的项目中,父组件传递数据的格式,必须和子组件要求的格式保持一致,否则就认为代码出错
自 React v15.5 起,
React.PropTypes
已移入另一个包中。请使用prop-types
库 代替
安装 prop-types
npm install prop-types --save
引入
import PropTypes from 'prop-types';
使用
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
// 1 引入 prop-types
import PropTypes from 'prop-types';
let HomeNav = (props) => {
return <h1> 导航为 {props.color} 数量为 {props.nums} </h1>
}
// 2 指定要求接收的数据格式
HomeNav.propTypes ={
color:PropTypes.string,
nums:PropTypes.number
}
class Home extends Component {
state = {
color: "blue",
nums:100
}
render() {
return (
<div>
<HomeNav {...this.state}></HomeNav>
</div>
)
}
}
ReactDOM.render(<Home />, document.getElementById('root'))
props插槽 props.children
props.children
可以实现类似vue中的插槽功能
子组件
let HomeNav = (props) => {
return (
<div>
<div>标题</div>
<div>{props.children}</div>
</div>
)
}
父组件
class Home extends Component {
render() {
return (
<div>
<HomeNav {...this.state}>
{/* 这里放动态插入的内容 */}
<div>在这个父组件中的子组件内插入一个div包含的内容,需要在子组件中{props.children}才能显示</div>
</HomeNav>
</div>
)
}
}
完整代码
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
let HomeNav = (props) => {
return (
<div>
<div>标题</div>
<div>{props.children}</div>
</div>
)
}
class Home extends Component {
render() {
return (
<div>
<HomeNav {...this.state}>
{/* 这里放动态插入的内容 */}
<div>在这个父组件中的子组件内插入一个div包含的内容,需要在子组件中{props.children}才能显示</div>
</HomeNav>
</div>
)
}
}
ReactDOM.render(<Home />, document.getElementById('root'))
state 和 props 对比
相同
1.二者都作为 React 内更新视图的依据,只有它们变化时,React 才会进行相应的更新。
2.二者都不可以通过直接赋值的方式更新。
不同
1.更新方式不同:state 通过
setState
方法更新(只能在组件内部更新),props 则通过更新传入的值实现(组件内不可变)。2.state 只维护组件内部的状态,props 让外部维护组件的状态。
总结
尽量少用
state
,尽量多用props
,这样既能提高组件的可复用性,又能降低维护成本
事件
React
元素的事件处理和DOM
元素的很相似,但是有一点语法上的不同:
- React 事件的命名采用小驼峰式(camelCase),而不是纯小写。
- 使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。
传统HTML绑定事件
<button onclick="activateLasers()">
Activate Lasers
</button>
React中
<button onClick={this.handleClick}>
Activate Lasers
</button>
事件中的this
观察以下的this的值 它是
undefined
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class Home extends Component {
state = {
msg: "大家好呀"
}
handleClick() {
// undefined
console.log(this);
}
render() {
return (
<div>
<div onClick={this.handleClick} >小标题</div>
</div>
)
}
}
ReactDOM.render(<Home />, document.getElementById('root'))
因为在
react
中,绑定事件 不是原生HTML的dom元素,onClick
只是react帮我们做的一个中间的映射,那么它在处理事件的时候,是不会处理this
的指向的,需要我们手动处理这个this
处理方式
1.绑定事件的时候 使用
bind
来锁定this
<div onClick={this.handleClick.bind(this)} >小标题</div>
2.在构造函数中 使用
bind
修改this
指向
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class Home extends Component {
state = {
msg: "大家好呀"
}
constructor() {
super();
// 重新绑定this
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log(this);
}
render() {
return (
<div>
<div onClick={this.handleClick} >小标题</div>
</div>
)
}
}
ReactDOM.render(<Home />, document.getElementById('root'))
**3.将事件的执行,改成 箭头函数 的方式 **
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class Home extends Component {
state = {
msg: "大家好呀"
}
// 修改为箭头函数的方式
handleClick = () => {
console.log(this);
}
render() {
return (
<div>
<div onClick={this.handleClick} >小标题</div>
</div>
)
}
}
ReactDOM.render(<Home />, document.getElementById('root'))
事件传参
执行类似以前HTML的dom事件传参一样的功能
<button οnclick="show('大家好')"></button>
React中的写法
1.不推荐,容易导致意外情况
<button onClick={() => { this.handleClick('red') }}>红色</button>
2.推荐写法
<button onClick={ this.handleClick.bind(this,'red') }>红色</button>
受控和非受控表单
受控是指受React控制。可以理解为绑定了
value
或者checked
属性的表单就是受控表单。也可以理解为受控表单就是实现了双向绑定。
受控表单
对
input
标签和checkbox
标签进行改造
input 受控
必须同时给与
onChange
事件和绑定value
属性
<input type="text" value={this.state.inpValue} onChange={this.handleChangeValue.bind(this)} />
逻辑中 执行给
inpValue
赋值
handleChangeValue(e) {
this.setState({
inpValue: e.currentTarget.value
})
}
checkbox 受控
必须同时给与
onChange
事件和绑定value
属性
<input type="checkbox" checked={this.state.isChecked} onChange={this.handleChkChecked.bind(this)} />
逻辑中 执行给
inpValue
赋值
handleChkChecked(e){
this.setState({
isChecked:e.currentTarget.checked
})
}
更多受控标签的说明
元素 | 值 | 绑定事件 | h获取值 |
---|---|---|---|
<input type="text" /> | value="string" | onChange | event.target.value |
<input type="checkbox" /> | checked={boolean} | onChange | event.target.checked |
<input type="radio" /> | checked={boolean} | onChange | event.target.checked |
<textarea /> | value="string" | onChange | event.target.value |
<select /> | value="option value" | onChange | event.target.value |
ref转发
在函数式组件中的第二个参数接收父组件传递过来的ref
export default React.forwardRef(
function Two(props, ref) {
const p = React.createRef();
return (
<div>
<h1>Two组件</h1>
<p>test</p>
<Box ref={ref}/>
</div>
)
}
)
非受控表单 ref
很多时候,我们不得不去操作dom元素。如 :
- 管理焦点,文本选择或媒体播放。
- 触发强制动画。
- 集成第三方 DOM 库。
React 16.3 版本后使用
React.createRef()
API 来创建ref
使用步骤
1.在构造函数中获取实例
// myRef 为自定义的名字
this.myRef = React.createRef();
2.在
render
函数中将myRef绑定到对应的html标签或者React组件上
<input type="text" ref={this.myRef} />
3.后期可以通过 以下代码来获取 该实例的引用
this.myRef.current
默认值
有时候我们希望给非受控表单一个初始化值,同时又不希望将它变成受控表单。此时可以使用
defaultValue
和defaultChecked
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class App extends Component {
state = {
msg: "2008",
isChecked: true
}
render() {
return (
<div >
{/* 添加了 defaultValue */}
<input type="text" defaultValue={this.state.msg} />
{/* 添加了 defaultChecked */}
<input type="checkbox" defaultChecked={this.state.isChecked} />
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'))
设置输入框获得焦点
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class App extends Component {
constructor() {
super();
this.inp = React.createRef();
}
handleClick() {
this.inp.current.focus();
}
render() {
return (
<div >
<input type="text" ref={this.inp} /> <button onClick={this.handleClick.bind(this)}>设置获取焦点</button>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'))
代码分割
App同级文件夹util中alert.js文件
uitl/alert.js
console.log('文件的代码执行了.....');
export default function(msg){
alert(msg);
}
App.js
import React, { Component } from 'react'
// import alertFunc from './util/alert'
export default class App extends Component {
render() {
return (
<div>
<button onClick={this.btnAction1}>按钮1</button>
<button onClick={this.btnAction2}>按钮2</button>
</div>
)
}
btnAction1 = async () => {
// import('./util/alert')
// .then( ( {default : alertFunc} )=>{
// alertFunc('hello');
// })
const {default : alertFunc} = await import(/* webpackChunkName: 'alert' */'./util/alert');
alertFunc('hello');
}
btnAction2 = async () => {
const {default : conFunc} = await import(/* webpackChunkName: 'console' */'./util/console');
conFunc('test');
}
}
Fragment的使用
import React, { Component, Fragment } from "react";
import One from "./One";
export default class App extends Component {
render() {
return (
<Fragment>
<One />
</Fragment>
);
}
}
children
Wrap.js
import React, { Component } from 'react'
import PropTypes from 'prop-types'
export default class Wrap extends Component {
constructor(props) {
super(props);
console.log(props);
}
render() {
return (
<div className="wrap">
<h1>wrap组件</h1>
{this.props.children}
</div>
)
}
// children 可以为任意类型
static propTypes = {
children: PropTypes.node
}
}
App.js
使用children可以让父组件中子组件内的内容显示,类似vue中的插槽
import React, { Component } from 'react';
import Wrap from './components/Wrap'
class App extends Component {
render() {
return (
<div className="app">
<Wrap hArr={(
<React.Fragment>
<h1>hello wrold</h1>
<h2>hello wrold</h2>
<h3>hello wrold</h3>
<h4>hello wrold</h4>
</React.Fragment>
)}>
</Wrap>
</div>
);
}
}
export default App;
Context的使用
构建context的类型 const Context = React.createContext({})
$_开头的属性都是内部私有的属性在js中
Consumer:数据的消费者
Provider:数据的提供者
contextType保留在继承的Component中 所以函数式组件不能 组件.contextType 只能通过Consumer来使用数据
使用context
1.创建context 例:const ColorContext = React.createContext({/默认属性/color:‘blue’})
2.向整颗组件树提供共享的context数据 import ColorContext from ‘./data/…’ <ColorContext.Provider></ColorContext.Provider>
3.获取数据 通过Consumer
1.在data/BorderContext.js、ColorContext.js文件中构建context类型
React.createContext({})
import React from 'react'
// 1.构建context的类型
const BorderContext = React.createContext({
border: '1px solid #999'
});
export default BorderContext;
import React from 'react'
// 1.构建context的类型
const ColorContext = React.createContext({
color: 'blue'
});
export default ColorContext;
2.向整颗组件树提供共享的context数据
App.js
数据通过value值携带
import React, { Component } from "react";
import One from './components/One'
import Two from './components/Two'
import './style.css'
import ColorContext from './data/ColorContext'
import BorderContext from './data/BorderContext'
export default class App extends Component {
render() {
// 2.向整颗组件树提供共享的context数据
return (
<BorderContext.Provider value={{border: '5px dashed #68a'}}>
<div>
<ColorContext.Provider value={{color: 'red'}}>
<One/>
<Two/>
</ColorContext.Provider>
<Three/>
</div>
</BorderContext.Provider>
)
}
}
3.获取数据
One.js\Two.js
获取数据的两种方式
1.通过context实例中的Consumer属性
注意:Consumer中包裹一个函数并返回内容 函数形参就是value传递过来的数据,可以嵌套使用获取多个value值,第二种方式就不行了
import React, { Component } from "react";
import ColorContext from "../data/ColorContext";
import BorderContext from "../data/BorderContext";
class One extends Component {
render() {
return (
<div className="box">
<h1>One</h1
{/* 3.获得数据 */}
<BorderContext.Consumer>
{({ border }) => (
<ColorContext.Consumer>
{({ color }) => (
<mark style={{ background: color, border: border }}>test</mark>
)}
</ColorContext.Consumer>
)}
</BorderContext.Consumer>
</div>
);
}
getDom = (value) => {
return <mark style={{ background: value.color }}>test</mark>;
};
}
export default One;
2.通过设置组件的contxetType属性
Two.contextType = 导入的那个context实例
在构造函数的第二个参数中获取通过父组件Provider中value属性共享过来的数据
import React, { Component } from 'react'
import ColorContext from '../data/ColorContext'
export default class Two extends Component {
constructor(props, context) {
super(props);
console.log(context);
}
render() {
return (
<div className="box">
<h1>Two</h1>
</div>
)
}
}
// ColorContext是导入进来的context实例
Two.contextType = ColorContext;
组件传值
在
React
中,数据的传递方式有以下几种
- 父传子 通过属性
- 子传父 通过事件
- 兄弟间传值 先传给父再传给兄弟
- 复杂关系
redux
父传子
在类组件中接收
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class HomeTop extends Component {
render() {
return (
<h1>屋顶的颜色是 {this.props.acolor} 尺寸 {this.props.asize}</h1>
)
}
}
class Home extends Component {
state = {
color: "blue",
size: 100
}
render() {
return (
<div>
<HomeTop acolor={this.state.color} asize={this.state.size} ></HomeTop>
</div>
)
}
}
ReactDOM.render(<Home />, document.getElementById('root'))
需要注意的是,改成 函数式组件, props需要在子组件上通过形参的方式接收和使用,不再是通过
this.props
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
// 函数式组件 通过props形参来接收
function HomeTop(props) {
return (
<h1>屋顶的颜色是 {props.acolor} 尺寸 {props.asize}</h1>
)
}
class Home extends Component {
state = {
color: "blue",
size: 100
}
render() {
return (
<div>
<HomeTop acolor={this.state.color} asize={this.state.size} ></HomeTop>
</div>
)
}
}
ReactDOM.render(<Home />, document.getElementById('root'))
子传父
通过触发事件的方式
1.在父组件中 给子组件挂载一个自定义事件
onChangeColor
<GreenBtn onChangeColor={this.changeColor}></GreenBtn>
2.子组件自己触发事件
const GreenBtn = (props) => {
setInterval(() => {
// 触发 父组件上的自定义事件
props.onChangeColor("green");
}, 1000);
return (
<button >绿色</button>
)
}
3.父组件上监听到了事件触发
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
const GreenBtn = (props) => {
setInterval(() => {
// 触发 父组件上的自定义事件
props.onChangeColor("green");
}, 1000);
return (
<button >绿色</button>
)
}
class App extends Component {
state = {
color: "red"
}
// 监听事件触发
changeColor = (color) => {
this.setState({
color
})
}
render() {
return (
<div style={{ backgroundColor: this.state.color }}>
<GreenBtn onChangeColor={this.changeColor}></GreenBtn>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'))
兄弟传值
思路是
组件A ->父组件->组件B
- 组件A通过触发事件,将数据传递个父组件
- 父组件通过属性props的方式将数据传递给子组件B
组件A
const GreenBtn = (props) => {
const handleClick = () => {
props.onChangeColor("green");
}
return (
<button style={{ color: props.color }} onClick={handleClick}>绿色</button>
)
}
父组件
class App extends Component {
state = {
color: "red"
}
changeColor = (color) => {
this.setState({
color
})
}
render() {
return (
<div style={{ backgroundColor: this.state.color }}>
<GreenBtn onChangeColor={this.changeColor} color={this.state.color} ></GreenBtn>
<RedBtn onChangeColor={this.changeColor} color={this.state.color} ></RedBtn>
</div>
)
}
}
组件B
const RedBtn = (props) => {
const handleClick = () => {
props.onChangeColor("red");
}
return (
<button style={{ color: props.color }} onClick={handleClick}>红色</button>
)
}
完整代码
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
// 1 组件A
const GreenBtn = (props) => {
const handleClick = () => {
props.onChangeColor("green");
}
return (
<button style={{ color: props.color }} onClick={handleClick}>绿色</button>
)
}
// 2 组件B
const RedBtn = (props) => {
const handleClick = () => {
props.onChangeColor("red");
}
return (
<button style={{ color: props.color }} onClick={handleClick}>红色</button>
)
}
// 3 组件C
class App extends Component {
state = {
color: "red"
}
changeColor = (color) => {
this.setState({
color
})
}
render() {
return (
<div style={{ backgroundColor: this.state.color }}>
<GreenBtn onChangeColor={this.changeColor} color={this.state.color} ></GreenBtn>
<RedBtn onChangeColor={this.changeColor} color={this.state.color} ></RedBtn>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'))
生命周期
React
组件的生命周期可以分为3种状态
- 挂载时
- 更新时
- 卸载时
挂载
当组件实例被创建并插入
DOM
中时,其生命周期调用顺序如下
- constructor()
- static getDerivedStateFromProps() 了解即可
- render()
- componentDidMount()
constructor
组件的构造函数,在组件被创建的时候会执行,一般有以下作用:
1.通过给
this.state
赋值对象来初始化内部state
。(不要使用this.setState()
来初始化数据)
this.state={key:val};
2.为事件处理函数绑定实例
this.handleClick=this.handleClick.bind(this);
render
render
函数负责将模板渲染到页面,一般来说,当props
和state
发生改变时,都会重新触发render
,需要注意的是,不要在render函数中 修改props
和state
,否则会导致死循环!!
componentDidMount
componentDidMount
会在组件挂载后(插入 DOM
树中)立即调用。依赖于 DOM 节点的初始化应该放在这里。一般用来在这里发送异步请求。
更新
当组件的 props 或 state 发生变化时会触发更新,会按顺序调用以下的生命周期事件
- static getDerivedStateFromProps() 了解即可
- shouldComponentUpdate()
- render()
- getSnapshotBeforeUpdate() 了解即可
- componentDidUpdate()
shouldComponentUpdate
当props
和state
发生改变时触发,返回true
则表示允许执行 render
,返回 false
这表示 不允许运行render
接收两个参数,nextState
和nextProps
,表示修改后的 state
和props
值。一般用在提高性能使用。如:
当state
中的某个值为偶数才触发render
,为奇数则不触发。
renders
同上述说明
componentDidUpdate
componentDidUpdate()
会在更新后会被立即调用。首次渲染不会执行此方法
当组件更新后,可以在此处对 DOM 进行操作。 如 判断是否触底了,再进行其他操作。
会接收3个参数
- prevProps 修改前的
props
对象 - prevState 修改前的
state
对象 - snapshot 了解
卸载时
componentWillUnmount
在组件卸载及销毁之前直接调用,在此方法中执行必要的清理操作,例如,清除 timer,取消网络请求等。也不要在该事件中 执行 setState
操作,无效。
不用常用的生命周期事件
样式
react中可以使用以下方式为元素添加样式
1.行内 无法实现伪类
<div style={{backgroundColor:"red"}} >行内样式</div>
2.全局样式 导致全局污染
import './css/index.css'
3.styled-jsx
4.css module(推荐)
CSS module
1.安装node-sass 依赖包
npm install --save-dev node-sass
2.添加 App.module.scss
文件,文件名中 module必须加上,不然识别不了这个模块文件,加入以下内容
.app {
.demo {
color:red
}
}
3.通过 import 方式引入 scss 文件
import AppCSS from "./App.module.scss"
4.添加样式
<div className={AppCSS.app}>
<h1 className={AppCSS.demo}>123</h1>
</div>
使用css module,每个单独生成的样式都有单独的命名空间,样式局部生效
styled-jsx
实现类似vue中,局部样式
安装依赖
customize-cra 负责合并webpack配置文件
styled-jsx 负责 实现局部样式
react-app-rewired 负责重新改写 项目的运行命令
npm install customize-cra styled-jsx react-app-rewired --save
新建配置文件 config-overrides.js
填入以下内容
// config-overrides.js
const { override, addBabelPlugins } = require('customize-cra');
module.exports = override(
...addBabelPlugins(
[
"styled-jsx/babel"
]
)
)
修改package.json
配置文件的script
字段
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-scripts eject"
}
在组件中 直接写样式
<div className="App">
<Header></Header>
<h1 className="hh11">我变颜色啦</h1>
<style jsx>{
`
.hh11{
background-color:red;
}
`
}
</style>
</div>
完整代码
import React from 'react';
import './App.css';
const Header = () => {
return (
<div>
<h1 className="hh11">我没有变颜色哦</h1>
</div>
)
}
function App() {
return (
<div className="App">
<Header></Header>
<h1 className="hh11">我变颜色啦</h1>
<style jsx>{
`
.hh11{
background-color:red;
}
`
}
</style>
</div>
);
}
export default App;
添加less支持
1.添加依赖
npm install styled-jsx-plugin-less@alpha less --save
2.修改配置文件 config-overrides.js
// config-overrides.js
const { override, addBabelPlugins } = require('customize-cra');
module.exports = override(
...addBabelPlugins(
[
"styled-jsx/babel",
{ "plugins": ["styled-jsx-plugin-less"] }
]
)
)
3.编写less语法
<style jsx>
{
`
.hh11{
background-color:red;
span{
color:blue;
}
}
`
}
</style>
React 路由
React Router
安装 路由
npm install react-router-dom --save
导入依赖
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
完整代码
import React from "react";
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
function Index() {
return <h2>Home</h2>;
}
function About() {
return <h2>About</h2>;
}
function Users() {
return <h2>Users</h2>;
}
function AppRouter() {
return (
<Router>
<div>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about/">About</Link>
</li>
<li>
<Link to="/users/">Users</Link>
</li>
</ul>
</nav>
<Route path="/" exact component={Index} />
<Route path="/about/" component={About} />
<Route path="/users/" component={Users} />
</div>
</Router>
);
}
export default AppRouter;
说明
从上述案例也可以看出,在
react-route-dom
中,存在以下几个常用的对象
名称 | 作用 |
---|---|
BrowserRouter | 使用传统的url模式 |
HashRouter | 使用哈希路由的模式 |
Route | 用来装路由对应的内容 |
Link | 用来指定路由路径的 |
Swich | 只可以匹配到一个路由 |
Redirect | 路由重定向 |
exact | 精确匹配路由 |
match | 路由对象的一个存放路由参数的的属性 |
location | 路由对象的一个存放URL信息的属性 |
history | 路由对象的一个控制路由跳转的属性 |
component | 路由渲染的一种方式 |
render | 路由渲染的一种方式 |
children | 路由渲染的一种方式 |
案例 1
1.导入路由必备对象
// 1 导入 哈希路由 路由-内容标签 路由-链接标签
import { HashRouter as Router, Route, Link } from "react-router-dom";
function Index() {
return <h2>Home</h2>;
}
function About() {
return <h2>About</h2>;
}
function Users() {
return <h2>Users</h2>;
}
3.使用路由
function AppRouter() {
return (
{/* 1 使用Router将Link 和 Route包含起来 */}
<Router>
<div className="route">
<nav>
<ul>
{/* 2 导航链接 */}
<li> <Link to="/">Home</Link> </li>
<li> <Link to="/about/">About</Link> </li>
<li> <Link to="/users/">Users</Link> </li>
</ul>
</nav>
<div className="content">
{/* 3 链接对应的内容 */}
<Route path="/" exact component={Index} />
<Route path="/about/" component={About} />
<Route path="/users/" component={Users} />
</div>
</div>
</Router>
);
}
常见的路由标签和路由属性
React的路由默认匹配规则 类似:
if(link.indexOf("/")){
...
}
if(link.indexOf("/about/")){
...
}
当 在route
上加上 exact
属性后,变成
if( link === "/" ){
...
}
if( link === "/about/" ){
...
}
在 1 的基础上 不加 exact
加 Switch
后 变成了
<Switch>
{/* 链接对应的内容 */}
<Route path="/" component={Index} />
<Route path="/about/" component={About} />
<Route path="/users/" component={Users} />
</Switch>
它的原理类似:if-else if ...
if(link.indexOf("/")){
...
}
else if(link.indexOf("/about/")){
...
}
有时候,为了防止用户输入一个不存在的路由,我们会设置一个 404组件
function PageNotFound() {
return <h2>404啦</h2>
}
<Switch>
<Route path="/" exact component={Index} />
<Route path="/about/" component={About} />
<Route path="/users/" component={Users} />
{/* 设置一个404页面 */}
<Route path="/404/" component={PageNotFound} />
</Switch>
因为,用户不可能自己输入一个 url:404
,用户更多的是输入一些错乱的路径 dfasdfsdf
等
因此,我们需要一个重定向 Redirect
import {Redirect } from "react-router-dom";
<Switch>
<Route path="/" exact component={Index} />
<Route path="/about/" component={About} />
<Route path="/users/" component={Users} />
<Route path="/404/" component={PageNotFound} />
{/* 可以匹配到除以上的路由规则外的所有路由 */}
<Redirect to="/404/" ></Redirect>
</Switch>
渲染方式
在React的中,有3种渲染方式
- component 每次都会触发组件的对应的生命周期
- render 内联模式渲染 性能会更高 props需要传递到函数内
- children 会一直渲染 不管匹配模式
<Route path="/about/" component={About} />
<Route path="/about/" render={(props) => <About {...props} />}/>
<Route path="/about/" children={() => <About/>}/>
匹配参数
有时候我们需要在链接中和内容中传递参数
通过 route
标签的 path属性来指定参数
<Route path="/users/:id/" exact component={UserDetail} />
通过 props属性中的 history
对象来进行 逻辑跳转
const {id}=e.currentTarget.dataset;
props.history.push(`/users/${id}/`);
通过 props
对象的match
对象来获取参数
class UserDetail extends Component {
render() {
const { id } = this.props.match.params;
return <h1>{id}</h1>
}
}
<Route path="/one/:id" component={Main}/>
<Route path="/two" render={(props)=>{
const obj = {
a: 1,
b: 2,
c: 3,
d: 4,
val: '123'
}
return <All {...obj} {...props}/>
}}/>
Redux
redux简单例子实现
效果图
目录结构
主入口index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
// 1. 引入一个 连接仓库和组件的连接组件
import { Provider } from "react-redux"
// 2. 引入仓库
import store from "./store"
//3. 配置到App节点上
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
App.js
import React, { Component } from 'react'
// 1. 引入连接器 将属性和行为 注入到组件中
import {connect} from "react-redux"
// 引入 actions
import { addAction,subAction} from "./store/actionCreator"
class App extends Component {
render() {
return (
<div>
<button onClick={this.props.addNum}>+</button>
{this.props.num}
<button onClick={this.props.subNum}>-</button>
</div>
)
}
}
// 将仓库中的 数据 映射到 组件中
const mapStateToProps = (state)=>{
return {
num: state.numReducer.num
}
}
// 将行为 注入到 组件中
const mapDispatchToProps = (dispatch)=>{
return {
// +
addNum:function(){
dispatch(addAction);
},
subNum:function(){
dispatch(subAction);
}
// -
}
}
export default connect(mapStateToProps,mapDispatchToProps)(App)
store/index.js
// 1. 引入 创建 仓库的 api
import { createStore } from "redux"
// 2. 引入 reducer 初始化的数据以及纯函数的操作
import rootReducer from "./reducer"
// 创建仓库 并且导出仓库
export default createStore(rootReducer);
store/reducer/index.js合并reducer
// 1. 引入合并reducer 的函数
import { combineReducers } from "redux"
//2. 引入合并的reducer
import numReducer from "./numReducer"
//3. 导出合并之后 reducer
export default combineReducers({numReducer:numReducer})
store/reducer/numReducer.js
import { ADD,SUB} from "../actionTypes"
const defaultState = {
num:888
}
export default function(state=defaultState,action){
// 纯函数操作
let newState = Object.assign({},state);
switch(action.type){
case ADD:
newState.num = state.num + action.payload.num;
break;
case SUB:
newState.num = state.num + action.payload.num;
break;
default:
break;
}
return newState;
}
store/actionCreator/index.js
提取
// { type:"ADD",payload:{ num:1 }}
import {ADD,SUB} from "../actionTypes"
const addAction = { type: ADD, payload: { num: 1 } };
const subAction = { type: SUB, payload: { num: -1 } };
export { addAction, subAction };
store/actionTypes/index.js
同上 可以提取也可以不提取
const ADD = "ADD"
const SUB = "SUB"
export { ADD,SUB}
Hook
react Hook 是react 新推出的功能
useState
正常版本 class 创建组件 定义state 用setState 来改变组件样式
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
</div>
);
}
}
无状态函数组件 则可以使用useState来添加状态
//1. 引入一个 hook useState
import React, { useState } from "react";
export default function App() {
// num 是 相当于 类组件中 state 中的变量 setNum 改变变量的函数
// useState 是 状态的钩子 括号里面的值 是 num 的初始值
let [num, setNum] = useState(999);
return (
<div>
<button
onClick={(params) => {
// setNum 相当于
// this.setState({ num : num + 1})
setNum(num + 1);
}}
>
+
</button>
{num}
<button
onClick={(params) => {
// this.setState({num:num-1})
setNum(num - 1);
}}
>
-
</button>
</div>
);
}
多个state的情况
//1. useState 允许在函数式组件中使用 class 中的状态
// 引入 钩子函数 useState
import React, { useState } from "react";
export default function App() {
// useState hook 中 有多个 state 的情况
// 数字
let [num, setNum] = useState(888); // 888 是 num 初始化值 setNum 改变 num的函数
// 字符串
let [title, setTitle] = useState("标题");
// 对象
let [todos, setTodos] = useState([
{ id: 1, text: "事项一", completed: true },
{ id: 2, text: "事项二", completed: true },
{ id: 3, text: "事项三", completed: true },
]);
return (
<div>
{/* setState({ num: num+1}) */}
<button
onClick={() => {
setNum(num + 1);
}}
>
+
</button>
<h1>{title}</h1>
{num}
<button
onClick={() => {
setNum(num - 1);
}}
>
-
</button>
{/* Todo list */}
<ul>
{
todos.map(v=><li key={v.id}>{v.text}</li>)
}
</ul>
</div>
);
}
hook版本的todos
// 引入 useState hook
import React, { useState } from "react";
export default function App() {
// let todos = [{
// id:1,
// text:"😀",
// completed:false
// },{
// id:2,
// text:"☀",
// completed:false
// }]
// todos hook
let [todos, setTodos] = useState([
{
id: 1,
text: "😀",
completed: false,
},
{
id: 2,
text: "☀",
completed: false,
},
]);
// inputVal
let [inputVal, setInputVal] = useState("");
return (
<div>
<div>
<label htmlFor="inp">输入</label>
<input
id="inp"
value={inputVal}
onChange={(e) => {
console.log(e.target.value);
// this.setState({ inputVal: e.target.value})
setInputVal(e.target.value);
}}
></input>
<button
onClick={() => {
if(!inputVal){
alert("输入不能为空!!!");
return false;
}
// 深拷贝
let newTodos = JSON.parse(JSON.stringify(todos));
newTodos.push({
id: Date.now(),
text: inputVal,
completed: false,
});
// 3个
setTodos(newTodos);
console.log(newTodos);
}}
>
添加 todos
</button>
</div>
{/* todos 的列表的开始 */}
<div>
{todos.map((v, index) => (
<div key={v.id}>
{v.text}
<button
onClick={(params) => {
let newTodos = JSON.parse(JSON.stringify(todos));
// 删除todos
newTodos.splice(index, 1);
setTodos(newTodos);
}}
>
删除
</button>
</div>
))}
</div>
{/* todos 的列表的结束 */}
</div>
);
}
useEffect
类组件中生命周期的实现
import React, { Component } from 'react'
export default class App extends Component {
state = {
num : 888
}
componentDidMount(){
// 在组件 挂载到 dom 树上的时候 修改页面的标题
document.title = "effect hook 例子";
}
// 组件更新的时候
componentDidUpdate(){
document.title = this.state.num;
}
handleClick= (params) => {
this.setState({
num: this.state.num + 1
})
}
render() {
return (
<div>
<button onClick={this.handleClick}>+</button>
{this.state.num}
</div>
)
}
}
类组件移除事件的方式
import React, { Component } from "react";
class Demo extends Component {
eventClick = (params) => {
console.log("document 被点击了");
}
// 子组件中注册了一个事件
componentDidMount() {
document.addEventListener("click", this.eventClick);
}
// 组件卸载的时候 移除监听器
componentWillUnmount(){
document.removeEventListener("click",this.eventClick);
}
render() {
return <div>子组件</div>;
}
}
export default class App extends Component {
state = {
show: true,
};
handleClick = (params) => {
this.setState({
show: !this.state.show,
});
};
render() {
return (
<div>
<button onClick={this.handleClick}>销毁组件</button>
{this.state.show ? <Demo></Demo> : ""}
</div>
);
}
}
在函数式组件中使用useEffect
// useEffect 是生命周期的 Hook
// 它能够为函数式组件提供 生命周期的功能
import React, { useState, useEffect } from "react";
export default function App() {
let [num, setNum] = useState(888);
let [title, setTitle] = useState("");
// useEffect 其实就是 componentDidMount 和
// componentDidUpdate 合体
// 里面的回调函数 会在 componentDidMount 和componentDidUpdate 生命周期执行的时候运行
// 关注点的分离
// 调试时候会更方便
// 设置标题
useEffect(() => {
if (num === 888) document.title = "😀b( ̄▽ ̄)d";
else document.title = num;
});
// 添加监听时间
useEffect(() => {
document.addEventListener("click", function () {
console.log("document 被点击了");
});
});
return (
<div>
<button
onClick={() => {
setNum(num + 1);
// document.title = num + 1;
}}
>
+
</button>
<h1>{num}</h1>
</div>
);
}
vs code插件
vscode-styled-jsx
组件中的颜色代码高亮
ES7 React/Redux/GraphQL/… 快速创建组件等
Prettier shift+alt+f格式化代码
chrome插件
1.
React Developer Tools
2.
Redux DevTools