官方文档:react.docschina.org/
一、react简介
概述
用于构建用户界面的JavaScript库。主要用于构建UI,很多人认为React是MVC结构中的V(view)
MVC结构
- M:Model 模型
- V:View 视图 UI界面
- C:controller 控制器
- M:Model 模型
- V:View 视图 UI界面
- VM:view modle
目前主流的前端框架
- Vue.js 最火(关注度最高)
- React.js 最流行
- Angular.js
使用方法
- 浏览器直接使用
- 通过npm + webpack构建项目进行使用
- 脚手架,比如Create React App(这种方式用的比较多)
二、使用
基本使用
创建一个React.js组件 react.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<!-- 加载我们的 React 组件。-->
<!--<script src="script.js"></script> 如果写在这里,代码按照顺序执行,js文件里面取元素会取不到-->
</head>
<body>
<div id="container"></div>
<script src="./script.js"></script>
</body>
</html>
script.js 这里是原生的API调用
//定义一个Reactt.js组件
class Hello extends React.Component{
render(){
return React.createElement(
"div",
null,
"hello react"
);
}
}
ReactDOM.render(React.createElement(Hello),document.getElementById("container"))
jsx
jsx是一个 JavaScript 的语法扩展,在 React 中配合使用 JSX,JSX 可以很好地描述 UI 应该呈现出它应有交互的本质形式。JSX 可能会使人联想到模版语言,但它具有 JavaScript 的全部功能。
script.js中改为以下写法,但是浏览器还不支持这种新语法,因此要使用babel
//jsx 语法糖,在解析的时候还是会转为原生API的调用
class Hello extends React.Component{
render(){
return (
<div>
Hello React
</div>
);
}
}
ReactDOM.render(<Hello/>,document.getElementById("container"))
jsx+webpack+babel
由webpack使用babel-loader来去驱动babel编译jsx的指令
index.html不需要引入script.js,因为webpack会进行打包
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
</head>
<body>
<div id="container"></div>
</body>
</html>
安装Babel
preser-env是es6语法的插件
npm install --save-dev @babel/core @babel/cli @babel/preset-env @babel/preset-react
配置babel
.babelrc.json配置文件中添加预设和单独使用的插件
{
"presets": ["@babel/preset-env","@babel/preset-react"],
"plugins": []
}
安装babel-loader
npm install babel-loader --save-dev
配置babel-loader
在webpack的配置文件 webpack.config.js 中配置babel-loader,在 module的rules中配置,options不写是因为babel自己的配置文件中已经写过用什么预设了
const path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/main.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
module:{
rules:[
{
test:/\.js$/,
exclude:/(node_modules|bower_components)/,
use:{
loader:'babel-loader',
//options:{
//presets:['@babel/preset-env']
//}
}
}
]
}
};
使用npm安装react
之前都是在script中引入网络上的react,现在使用npm安装react
过程如下
现在工程中只有一个index.html一个页面,但有很多组件,通过放在容器中进行显示
将组件独立
jsx的基本语法
jsx中使用数值、字符串、boolean、数组等数据
return中必须要有一个根节点(与vue相同)
当使用 JSX 语法时,可以使用大括号在 JSX 中插入 JavaScript 表达式
三、组件之间数据传输
React组件中和数据有关的有三个东西(state,props,context)
1、组件自身数据
数据被挂载到组件的实例上
render函数每次被调用一次,页面就被再次绘制一次
- 1、每个class组件都可以设置自身的数据
//构造函数,实例对象一创建,构造函数就会被调用
constructor(){
super()
this.a = 100
}
- 2、组件自身的数据发生变化,不会引起视图改变(不会触发render函数) 自己手动调用render函数并不能改变数据,因为render函数是react组件生命周期函数之一
2、闭包内数据
闭包中的数据,也不会引起视图改变
import React, { Component } from 'react'
export default class ClosureData extends Component {
render() {
//console.log("render",this) selfdata
console.log("render is called")
var a = 100
return (
<div>
<h1>闭包内的数据</h1>
<p>{a}</p>
<p>
<input type="button" value="click me" onClick={()=>{ //闭包
a++
console.log(a)
}}/>
</p>
</div>
)
}
}
3、state中的数据
state中的数据发生改变后,会触发组件重新渲染(render函数会被调用)
模型中的数据一发生变化,视图中的数据就会发生变化,这是mvvm中的vm在进行操作,帮助我们不用去做各种dom操作,比如
var a = document.getElementById()
StateDate.js
import React, { Component } from 'react'
export default class StateData extends Component {
constructor(){
super()
//将数据放入到state中
this.state = {a:100}
}
add(){
console.log("add is called")
//this.state.a++ 这种写法错误
this.setState({a:this.state.a+1})
}
render() {
console.log("render is called")
return (
<div>
<h1>state中的数据</h1>
<p>{this.state.a}</p>
<p>
<input type="button" value="click me" onClick={this.add.bind(this)}/>
</p>
</div>
)
}
}
4、props
react中父组件可以通过属性将数据传递给子组件,在子组件中通过props
对象获取{this.props.xxx}
可以通过propstype来限定子组件获取的属性必须为哪种类型和是否传递,
- 首先安装prop-types
npm install prop-types -save - 在子组件中导入
import PropTypes from "prop-types" - 规定属性的类型以及是否必要
PropsChild.propTypes = {
name: PropTypes.string.isRequired //必须要传递一个string过来
}
子组件将获取到的数据的值传给自己的数据,不能在构造函数中直接使用this.props.xxx,因为这里props获取不到,在render里面才有props
constructor(){
super()
this.props.a //错误写法
}
render() {
console.log(this.props)
return (
<div>
<h3>这是子组件</h3>
<p>{this.props.a}</p>
</div>
)
}
可以在构造函数中添加一个props参数,以此获取props
constructor(props){
super()
this.state={
a: props.a
}
}
子组件不能直接修改父组件的数据,可以通过父组件向子组件传递一个函数,子组件中通过调用这个函数,将数据传递给父组件
PropsParent.js
import React, { Component } from 'react'
import PropsChild from "./PropsChild"
export default class PropsParent extends Component {
constructor(){
super()
this.state={
a:100
}
}
setA(a){
this.setState({a:a})
}
render() {
return (
<div>
<h1>这是父组件</h1>
<p>{this.state.a}</p>
------------------
<PropsChild a={this.state.a } setA={this.setA.bind(this)}/> {/*这里的this指向父类的实例*/}
</div>
)
}
}
PropsChild.js
import React, { Component } from 'react'
import PropTypes from "prop-types"
export default class PropsChild extends Component {
constructor(props){
super()
this.state={
a: props.a //获取父组件传递的值
}
}
add(){
this.setState({a:this.state.a + 1})
this.props.setA(this.state.a + 1) //根据父组件提供的函数,将自己修改后的值传回去
}
render() {
return (
<div>
<h3>这是子组件</h3>
<p>{this.props.a}</p>
<p>
<input type="button" value="click me" onClick={this.add.bind(this)}/>
</p>
</div>
)
}
}
四、react创建组件
1、使用class关键字创建组件
rcc
编写一个类继承于Component,并向外暴露
传参的一种方式
class中,如果没有定义构造函数,则默认提供一个无参的构造函数;如果自定义了一个构造函数,则必须调用super,只有调用了super以后才能使用this。
调用this.state将数据放入state管理,在state中的数据如果发生变化,会触发组件重新渲染
state数据的修改
不能直接修改State,而是通过setState来进行修改
state的更新可能是异步操作
调用setState,组件中的state数据并不会立即改变,setState只是把修改的状态放入到一个队列中,React会优化执行的时机,并且出于性能的原因,可能会将多次setState的状态修改合并成一次,所以不要依赖当前的state去计算下一个state
doAdd(){
this.setState({count:this.state.count + 1})
console.log("第一次修改",this.state.count)
this.setState({count:this.state.count + 1})
console.log("第二次修改",this.state.count)
this.setState({count:this.state.count + 1})
console.log("第三次修改",this.state.count)
//1 1 1
}
要解决这个问题,可以让setState()接收一个函数而不是一个对象,这个函数用state作为参数
doAdd(){ //点击一次按钮count加 3
this.setState(state =>({
count: state.count + 1
}),()=>{
console.log(this.state.count)
}) //4 回调函数,输出结果是4,说明是异步操作
this.setState(state =>({
count: state.count + 1
}))
this.setState(state =>({
count: state.count + 1
}))
}
2、使用函数创建组件
快捷方式(rsc)
编写一个function,返回jsx
import React, { Component } from 'react'
function ConstructorEmp(){
return (
<div>
<h1>构造函数创建组件</h1>
</div>
)
}
export default ConstructorEmp
函数组件中可以获取props,通过props获取父组件传来的数据。但是函数组件中没有this,也不能使用this.state
function ConstructorEmp(props){
return (
<div>
<h1>构造函数创建组件</h1>
<p>{props.name}</p>
<p>{props.age}</p>
<p>{props.gender}</p>
</div>
)
}
五、组件的渲染方式
条件渲染
通过if进行条件渲染
function Conditional(props){
const showGreeting = () => {
if(props.isLogin){
return(<div>用户已登录</div>)
}else{
return (<div>用户没有登录</div>)
}
}
return (
<div>{showGreeting()}</div>
)
}
使用&&进行条件渲染
function Conditional(props){
return props.isLogin && (<div>用户已登录</div>)
}
使用三元运算符
function Conditional(props){
return props.isLogin?(<div>用户已登录</div>):(<div>用户没有登录</div>)
}
组织组件的渲染
function Conditional(props){
if(!props.isLogin){
return null
}
return props.isLogin?(<div>用户已登录</div>):(<div>用户没有登录</div>)
}
列表渲染
key 帮助 React 识别哪些元素改变了,比如被添加或删除。因此你应当给数组中的每一个元素赋予一个确定的标识。
一个元素的 key 最好是这个元素在列表中拥有的一个独一无二的字符串。通常,我们使用数据中的 id 来作为元素的 key
当元素没有确定 id 的时候,万不得已你可以使用元素索引 index 作为 key
import React from 'react'
function List(props) {
return (
<ul>
{
props.emps.map((item,index)=>{
return (<li key={index}>{item.name}</li>)
})
}
</ul>
)
}
export default Listconst emps = [
{name :"tom",age:18,gender:"男"},
{name :"jack",age:20,gender:"男"}
]
ReactDOM.render(<List emps={emps}/>,document.getElementById("container"))
六、fragments
react.docschina.org/docs/fragme… 什么时候用——(想要去除多余的div标签时) 比如父组件要做一个表格,子组件是里面的各个格子,里面如果有div的话会出错,因此可以使用 下面这个标签
<React.fragments></React.fragments>
七、组合与继承
react.docschina.org/docs/compos…
props.children
不推荐继承
包含关系
有些组件无法提前知晓它们子组件的具体内容,这些组件使用一个特殊的 children prop 来将他们的子组件传递到渲染结果中
子组件
function FancyBorder(props) {
return (
<div >
{props.children}
</div>
);
}
父组件,FancyBorder标签之间的内容就是要传给子组件的内容
function WelcomeDialog() {
return (
<FancyBorder color="blue">
<h1 >
Welcome
</h1>
<p>
Thank you for visiting our spacecraft!
</p>
</FancyBorder>
);
}
少数情况下,你可能需要在一个组件中预留出几个“洞”。这种情况下,我们可以不使用 children,而是自行约定:将所需内容传入 props,并使用相应的 prop。
function SplitPane(props) {
return (
<div>
<div>
{props.left}
</div>
<div>
{props.right}
</div>
</div>
);
}
function App() {
return (
<SplitPane
left={
<Contacts />
}
right={
<Chat />
} />
);
}
评论列表练习
css命名冲突
当前的样式文件一旦在组件中引入后,就变成全局的样式,所有有时候会产生命名的冲突,可以使用css模块化解决这个问题
配置webpack中的css-loader,在webpack.config.js中开启css-loader模块化设置
www.webpackjs.com/loaders/css…
{
test: /\.css$/,
use: [{
loader: "style-loader"
}, {
loader: "css-loader",
options:{
modules:{
localIdentName:'[path][name]__[local]--[hash:base64:5]'
}
}
}]
}
但凡以元素名称作为选择器,都不会被模块化,id和class都会被模块化
八、reactstrap
reactstrap是基于bootstrap的,能更方便的使用bootstrap中的组件 reactstrap.github.io/?from=madew…
安装
在安装reactstrap之前要安装bootstrap
npm install --save bootstrap reactstrap
导入bootstrap.css
import "bootstrap/dist/css/bootstrap.min.css"
导入组件
import { Button } from 'reactstrap';
使用
join() 方法用于把数组中的所有元素放入一个字符串。
元素是通过指定的分隔符进行分隔的。
import React from 'react'
//因为开启了模块化,所以要用模块化的写法
import style from "bootstrap/dist/css/bootstrap.min.css"
import { Button } from 'reactstrap';
function ReactStrap() {
return (
<div>
<Button className={[style.btn,style["btn-primary"]].join(" ")}>primary</Button>{' '}
</div>
)
}
export default ReactStrap
九、事件处理
react.docschina.org/docs/handli…
class LoggingButton extends React.Component {
// 此语法确保 `handleClick` 内的 `this` 已被绑定。
// 注意: 这是 *实验性* 语法。
handleClick = () => {
console.log('this is:', this);
}// [Object Object]
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
);
}
}
这种写法会遇到不支持箭头函数的情况,需要使用@babel/plugin-proposal-class-properties插件
npm install --save-dev @babel/plugin-proposal-class-properties
在babelrc文件中配置插件
"plugins":[
@babel/plugin-proposal-class-properties
]
十、refs
react.docschina.org/docs/refs-a…
ref 是一个入口 允许我们直接访问DOM元素或组件实例。
创建refs
class MyComponent extends React.Component {
constructor(props) {
super(props);
//创建一个ref的引用
this.myRef = React.createRef();
}
render() {
return <div ref={this.myRef} />;
}
}
关联ref
<input type="text" ref={this.myRef}>
访问refs
当 ref 被传递给 render 中的元素时,对该节点的引用可以在 ref 的 current 属性中被访问。
const node = this.myRef.current;
ref 的值根据节点的类型而有所不同:
-
当 ref 属性用于 HTML 元素时,构造函数中使用 React.createRef() 创建的 ref 接收底层 DOM 元素作为其 current 属性。
-
当 ref 属性用于自定义 class 组件时,ref 对象接收组件的挂载实例作为其 current 属性。 你不能在函数组件上使用 ref 属性,因为他们没有实例。
//点击事件
clickHandle()=>{
console.log(this.myRef.current.value)
}
为DOM元素添加ref
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
// 创建一个 ref 来存储 textInput 的 DOM 元素
this.textInput = React.createRef();
this.focusTextInput = this.focusTextInput.bind(this);
}
focusTextInput() {
// 直接使用原生 API 使 text 输入框获得焦点
// 注意:我们通过 "current" 来访问 DOM 节点
this.textInput.current.focus();
}
render() {
// 告诉 React 我们想把 <input> ref 关联到
// 构造器里创建的 `textInput` 上
return (
<div>
<input
type="text"
ref={this.textInput} />
<input
type="button"
value="Focus the text input"
onClick={this.focusTextInput}
/>
</div>
);
}
}
为class组件添加ref
在父组件中点击按钮,使得子组件中的文本框获取焦点
将DOM refs暴露给父组件
使用ref转发
//refs转发。React.forwardRef()接收props和ref,
//可以将父组件的ref传递给子组件内的节点
const TextInput3 = React.forwardRef((props,ref) => {
return(
<div>
<input type="text" ref={ref}></input>
</div>
)
})
<TextInput3 ref={this.textInput3}/>
回调refs
你可以在组件间传递回调形式的 refs,就像你可以传递通过 React.createRef() 创建的对象 refs 一样。
import React, { Component } from 'react'
function CustomTextInput(props) {
return (
<div>
<input ref={props.inputRef} />
</div>
);
}
export default class PassCallBackRef extends Component {
handleClick(){
console.log("子组件中的数据:",this.inputElement.value)
}
render() {
return (
<div>
<CustomTextInput
inputRef={el => this.inputElement = el}
/>
<button onClick={this.handleClick.bind(this)}>点我获取数据</button>
</div>
)
}
}
在上面的例子中,Parent 把它的 refs 回调函数当作 inputRef props 传递给了 CustomTextInput,而且 CustomTextInput 把相同的函数作为特殊的 ref 属性传递给了 <input>。结果是,在 Parent 中的 this.inputElement 会被设置为与 CustomTextInput 中的 input 元素相对应的 DOM 节点,父组件可以获取子组件节点的数据。
回调ref 传递
功能:点击按钮获取子组件中的数据
import React, { Component } from 'react'
import { func } from 'prop-types'
function TextInput (props){
return(
<div>
<input type="text" ref={props.inputRef}/>
</div>
)
}
export default class PassCallBackRef extends Component {
HandleClick = ()=>{
console.log("子组件中的数据:",this.input.value)
}
render() {
return (
<div>
{/*将子组件作为参数传递给input */}
<TextInput inputRef={el=>{ this.input = el}}></TextInput>
<button onClick={this.HandleClick}>点我获取子组件的数据</button>
</div>
)
}
}
十一、受控组件与非受控组件
受控组件
假设我们现在有一个表单,表单中有一个input标签,input的value值必须是我们设置在constructor构造函数的state中的值,然后,通过onChange触发事件来改变state中保存的value值,这样形成一个循环的回路影响。也可以说是React负责渲染表单的组件仍然控制用户后续输入时所发生的变化。
十二、高阶组件
属性代理
使用refs获取组件实例
受控组件state抽取
用其他元素包装组件
使用反向继承实现高阶组件
反向继承:返回一个组件继承传入的被包裹组件
一旦一个类继承另一个类的时候,就自动拥有父类中所有非私有的成员信息
使用反向继承渲染劫持
高阶组件.js
const elementsTree = super.render()
let newProp = {style:{color:"red"}};
const props = {...elementsTree.props, ...newProp}
const newElement = React.cloneElement(elementsTree,props,)
十三、生命周期函数
react.docschina.org/docs/react-…
组件渲染与挂载blog.csdn.net/u012131835/…
我们把组件渲染,并且构造DOM元素插入到页面的过程称为组件的挂载。
构造函数
constructor函数只会执行一次
render()
页面中有一个真实的dom树,react中为了性能考虑,拷贝了一个虚拟的dom树,当DOM节点发生变化时,react会将两个DOM树进行比较,将有差异的地方进行更新
- render()中会构建虚拟DOM,进行diff算法,更新dom树
- 此时虚拟dom还没有渲染到页面上
- render会执行多次
componentDidMount
- 构建的虚拟DOM已经挂载到了页面上
- 只会执行一次
componentDidUpdate
- 组件完成更新,此时页面已经是最新的了
- 会执行多次
componentWillUnmount
- 组件实例将要被销毁,此时组件还可以被使用
- 通常在此函数中处理一些资源的释放
十四、hook
1、useState
- useState这个hook允许我们在函数组件中声明state
- useState中的set方法,只会在数据的地址发生改变的时候重新渲染
2、useEffect
相当于componentDidMount 和 componentDidUpdate
副作用分为需要清除的副作用和不需要清除的副作用
十五、addwebpackAlias 文件夹路径起别名
addwebpackAlias
const path = require("path")
const {addwebpackAlias} = require("cusomize-cra")
addwebpackAlias({
["@"]:path.resolve(_dirname,"src")
})