React 是什么
- React 是 facebook 推出的用于构建用户界面的前端 JS 框架
- https://reactjs.org/
- React 使用组件构建用户界面
组件是什么
一块区域,包含了 html css 以及 js
组件开发的优势
- 将一个庞大复杂的应用程序拆分成多个独立单元
- 组件之间互相独立,有利于应用程序的维护
- 组件可以重用,一次编写多地复用
React 开发环境搭建
基于 webpack 搭建
- package.json
{ "name": "code", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "@babel/core": "^7.13.8", "@babel/preset-env": "^7.13.9", "@babel/preset-react": "^7.12.13", "babel-loader": "^8.2.2", "html-webpack-plugin": "^4.5.2", "react": "^17.0.1", "react-dom": "^17.0.1", "webpack": "^4.46.0", "webpack-cli": "^3.3.12", "webpack-dev-server": "^3.11.2" } }
- webpack.config.js
const path = require('path') const HtmlWebpackPlugin = require ('html-webpack-plugin') module.exports = { // 开发模式 mode: 'development', // 不需要调试 devtool: 'none', // 入口文件 entry: './src/index.js', // 出口文件 output: { // 产出文件名 filename: 'main.js', // 产出路径 path: path.resolve('dist') }, // 服务器 devServer: { // 端口号 port: 3000, // 热更新 hot: true }, // 配置 loader module: { rules: [ { test: /\.js|jsx$/, // 不匹配 node_modules exclude: /node_modules/, use: [ { loader: 'babel-loader', options: { presets: ['@babel/ preset-env', '@babel/ preset-react' ] } } ] } ] }, // 配置 plugins plugins: [ new HtmlWebpackPlugin({ template: './src/index.html' }) ] }
- index.html
<div id="root"></div>
- index.js
import React from 'react' import {render} from 'react-dom' // 自定义组件 function App () { return <div>React</div> } render(<App />, document.querySelector ('#root'))
基于脚手架工具搭建
- 安装脚手架工具:
npm i create-react-app -g
- 创建指令:
create-react-app 项目名
JSX
JSX 基础语法
JSX 可以看做是 JS 语言的扩展,他既不是一个字符串也不是一个 HTML
它具备了 JS 所有的功能,同时还可以被转为 HTML 在界面上进行展示(react react-dom)
- 动态显示数据
const name = "GD"
function App() {
return (
<div>
<p>{name}</p>
<p>name</p>
</div>
);
}
export default App;
- 调用方法(自定义 + 内置)
function sayHi () {
return '大家好'
}
function App() {
return (
<div>
<p>{sayHi()}</p>
</div>
);
}
export default App;
- 支持表达式(支持三元表达式,不支持 if 语句)
const flag = false
function App() {
return (
<div>
<p>{flag ? '以登录' : '未登录'}</p>
</div>
);
}
export default App;
- 模板字符串
const name = "GD"
function App() {
return (
<div>
<p>{`hello, ${name}`}</p>
</div>
);
}
export default App;
- 注释
function App() {
return (
<div>
<p>{/** 这是注释的内容 */}</p>
</div>
);
}
export default App;
- 对象形式的数据
const obj = {
name: 'GD',
age: 18
}
function App() {
return (
<div>
<p>{JSON.stringify(obj)}</p>
</div>
);
}
export default App;
- JSX 本身就是一个表达式
const name = <div>拉勾教育</div>
function App() {
return (
<div>
<p>{name}</p>
</div>
);
}
export default App;
- JSX 添加属性
- 字符串属性,直接用双引号包裹
- 动态属性
const name = 100
function App() {
return (
<div>
<p title="自定义标题">添加字符串属性</p>
<p title={name}>添加动态属性</p>
</div>
);
}
export default App;
- JSX 添加子元素
function App() {
return (
<div>
{/* 这里书写的元素就是子元素 */}
</div>
);
}
export default App;
- JSX 只能有一个父元素 即使是单标签,也要正确的闭合
function App() {
return (
<div>
<img />
</div>
);
}
export default App;
JSX 事件操作
- 事件绑定
const handler = () => {
console.log('执行了');
}
function App() {
return (
<div>
<button onClick={handler}>点击触发</button>
</div>
);
}
export default App;
- 事件监听传参
const handler = (a, b) => {
console.log(a + b);
}
function App() {
return (
<div>
<button onClick={() => {handler(1, 2)}}>点击1</button>
<button onClick={handler.bind(null, 1, 2)}>点击2</button>
</div>
);
}
export default App;
- 获取事件对象
const handler = (ev) => {
console.log(ev);
}
function App() {
return (
<div>
<button onClick={handler}>触发1</button>
<button onClick={(ev) => {handler(ev)}}>触发2</button>
<button onClick={handler.bind(null)}>触发3</button>
</div>
);
}
export default App;
const handler = (a, b, ev) => {
console.log(a);
console.log(b);
console.log(ev);
}
function App() {
return (
<div>
<button onClick={handler.bind(null, 1, 2)}>触发3</button>
</div>
);
}
export default App;
- 功能总结
- 事件绑定
- 驼峰命名直接添加={事件监听的名称}
- 事件监听传参
- 利用箭头函数内部调用事件监听的时候传递实参
- 利用 bind 方法返回一个新的函数在事件发生时调用,此时也可以传递参数
- 获取事件对象
- 默认情况下不需要接受参数,且直接执行事件监听,此时他的第一个参数默认是 ev
- 利用箭头函数执行事件监听的时候,需要通过箭头函数将 ev 对象传递给事件监听函数进行使用
- 利用 bind 方法执行时,如果有传参那么最后一个参数默认就是 ev
- 事件绑定
JSX遍历数据
- JSX 当中可以直接将数组中的数据解构
const arr = [1, 2, 3]
function App() {
return (
<div>
{arr}
</div>
);
}
export default App;
数组里边的项为对象时可以作以下修改
const arr = [
{
id: 0,
name: 'GD',
age: 18
},
{
id: 1,
name: 'XGD',
age: 8
}
];
function App() {
const str = arr.map(item => {
return (
<li key={item.id}>
<span> {item.name} </span>
<span> {item.age} </span>
</li>
)
})
return <ul> {str} </ul>
}
export default App;
JSX 添加内联样式
- 设置样式的时候,应该将键值对放置于 {}
const styleObj = {
width: 100,
height: 100,
backgroundColor: 'green'
};
function App() {
return <div>
<div style={{width: '100px', height: '100px', background: 'red'}}> 样式处理1 </div>
<div style={styleObj}> 样式处理2 </div>
</div>
}
export default App;
- 内联样式默认无法支持伪类及媒体查询样式设置;如需使用需借助第三方的包帮助 — radium
- 导入 Radium 函数将当前需要支持伪类操作的组件包裹之后再导出
import Radium from 'radium' const styleObj = { width: 100, height: 100, backgroundColor: 'green', ':hover': { backgroundColor: 'yellow' } } function App() { return <div> <div style={{width: '100px', height: '100px', background: 'red'}}> 样式处理1 </div> <div style={styleObj}> 样式处理2 </div> </div> } export default Radium(App);
- 通过 Radium 设置媒体查询时,需要将当前组件所渲染的地方使用 StyleRoot 包裹
<!-- App.js --> import Radium from 'radium' const styleObj = { width: 100, height: 100, backgroundColor: 'green', ":hover": { backgroundColor: 'yellow' }, "@media (max-width: 1000px)": { width: 300 } } function App() { return <div> <div style={{width: '100px', height: '100px', background: 'red'}}> 样式处理1 </div> <div style={styleObj}> 样式处理2 </div> </div> } export default Radium(App); <!-- index.js --> import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; import {StyleRoot} from 'radium' ReactDOM.render( <React.StrictMode> <StyleRoot> <App /> </StyleRoot> </React.StrictMode>, document.getElementById('root') );
- 案例
import Radium from 'radium' const ButtonStyle = { base: { width: 150, height: 40, fontSize: 20, background: '#ffff' }, login: { background: 'green' }, logout: { background: 'orange' } } const isLogin = false function App() { return <div> <button style={[ ButtonStyle.base, isLogin ? ButtonStyle.login : ButtonStyle.logout ]}>按钮</button> </div> } export default Radium(App);
JSX 添加外联样式
- 全局外联样式
- 所有组件当中都可以直接进行使用
- 再添加 class 时,需要使用 className
// App.js function App() { return <div className={'box'}> 外联样式 </div> } export default App; // Test,js import React from 'react' function Test () { return <div className={'box'}>Test.js</div> } export default Test // index.js import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; import Test from './test' import './style.css' ReactDOM.render( <React.StrictMode> <App /> <Test /> </React.StrictMode>, document.getElementById('root') );
- 组件级别的外联样式
- 只有某一个组件可以进行使用
- 组件名.module.css
// Test.module.css .item { background-color: red; width: 200px; height: 200px; } // test.js import React from 'react' import style from './Test.module.css' function Test () { return ( <div> <div className={'box'}>Test.js</div> <p className={style.item}>这里使用了自己的样式</p> </div> ) } export default Test
- 借助第三方工具在 JS 中编写 CSS
- (CSS-IN-JS)
- npm i styled-components
import React from 'react' import style from './Test.module.css' import styled from 'styled-components' // 自定义标签 const SectionDiv = styled.div.attrs({ className: 'box1 box2' })` width: 100px; height: 100px; background-color: pink; ` function Test () { return ( <div> <div className={'box'}>Test.js</div> <p className={style.item}>这里使用了自己的样式</p> <SectionDiv /> </div> ) } export default Test
组件的创建
创建函数式组件
function App() {
return <div>
我就是函数式组件
</div>
}
export default App;
创建类式组件
- 必须继承 Compnent
- 必须实例化 render
- 组件名称首字母必须大写,在 React 中可以用于区分组件和普通的标记
import React, {Component} from 'react'
class About extends Component {
render () {
return (
<div>我是类式组件</div>
)
}
}
export default About
- 必须有且只能有一个根元素,同时支持占位符(两种方式)
import React, {Component, Fragment} from 'react'
class About extends Component {
render () {
return (
// 方式一
<Fragment>我是类式组件</Fragment>
// 方式二
<>我是类式组件</>
)
}
}
export default About
组件传参
函数式组件传参
在函数组件内可以接到外部的数据,内部直接访问即可
- 在组件身上添加属性然后传递数据
import About from './About' function App() { return <div> <About name={'GD'} age={100}/> </div> } export default App; // About.js import React from 'react' function About(props) { console.log(props); return ( <div> <p>{props.a}</p> <p>{props.b}</p> </div> ) } export default About
- 将数据统一管理,利用 … 操作直接传递给相应的组件
import About from './About' const obj = { name: 'rookie', age: 18 } function App() { return <div> <About {...obj}/> </div> } export default App; // About.js import React from 'react' function About({name, age}) { return ( <div> <p>{name}</p> <p>{age}</p> </div> ) } export default About
类式组件传参
类式组件内部的内部存在一个 props属性,外部传递的数据都放在这里保存,我们可以直接进行使用
- 在组件身上添加属性然后传递数据
import Header from './Header' function App() { return <div> <Header a={10} b={20}/> </div> } export default App; // Header.js import React, { Component } from 'react' class Header extends Component { render() { return ( <div> <p>{this.props.name}</p> <p>{this.props.age}</p> </div> ) } } export default Header
- 将数据统一管理,利用 … 操作直接传递给相应的组件
import About from './About' const obj = { name: 'rookie', age: 18 } function App() { return <div> <Header {...obj}/> </div> } export default App; // Header.js import React, { Component } from 'react' class Header extends Component { render() { const {name, age} = this.props return ( <div> <p>{name}</p> <p>{age}</p> </div> ) } } export default Header
组件设置默认值及类型校验
- 针对于函数组件来说,如果想要设置默认的 props 属性值,则直接通过 组件名称.defaultProps 来设置一个对象
- 针对于类组件来说,我们可以直接定义 static defaultProps 来管理需要设置默认值的属性即可
为什么要对 props 中的属性进行校验
JS 本身是弱类型语言,它里边的参数或者方法,在定义形参的时候我们没有办法直接对类型进行设置,这时候对于我们的使用来说有利有弊,比如说我这里的参数需要接受一个 number 类型的,但是传入的却是 string 类型的,那么一定会影响后续逻辑代码的实现,所以需要类型校验
import About from './About'
function App() {
return <div>
<About/>
</div>
}
export default App;
// App.js
import React from 'react'
import PropTypes from "prop-types";
function About({name, age}) {
return (
<div>
<p>{name}</p>
<p>{age}</p>
</div>
)
}
About.defaultProps = {
name: 111,
age: 188
}
About.propTypes = {
name: PropTypes.string
}
export default About
函数式组件设置默认值
import About from './About'
function App() {
return <div>
<About/>
</div>
}
export default App;
// About.js
import React from 'react'
function About({name, age}) {
return (
<div>
<p>{name}</p>
<p>{age}</p>
</div>
)
}
About.defaultProps = {
name: 'dgd',
age: 188
}
export default About
类式组件设置默认值
import Header from './Header'
const obj = {
name: 'rookie',
age: 18
function App() {
return <div>
<Header/>
</div>
}
export default App;
// Header.js
import React, { Component } from 'react'
class Header extends Component {
static defaultProps = {
name: '拉勾',
age: 3
}
render() {
const {name, age} = this.props
return (
<div>
<p>{name}</p>
<p>{age}</p>
</div>
)
}
}
export default Header
向组件传递 JSX
类组件传递 JSX
import Header from './Header'
function App() {
return <div>
<Header>
<p>Header 中的 P 标签</p>
</Header>
</div>
}
export default App;
// Header.js
import React, { Component } from 'react'
class Header extends Component {
render() {
return (
<div>
{this.props.children}
</div>
)
}
}
export default Header
函数组件传递 JSX
import About from './About'
function App() {
return <div>
<About>
<p>About 中的 P 标签</p>
</About>
</div>
}
export default App;
// About.js
import React from 'react'
function About(props) {
return (
<div>
{props.children}
</div>
)
}
export default About
组件布局实例
预期结构图
- header.js
function Header() {
return (<div className={"header"}>Header组件</div>)
}
export default Header
- home.js | list.js
// home.js
import Layout from './Layout'
function Home() {
return (<Layout>
<p>当前是 Home 页面</p>
</Layout>)
}
export default Home
// list.js
import Layout from './Layout'
function List() {
return (<Layout>
<p>当前是 List 页面</p>
</Layout>)
}
export default List
- footer.js
function Footer() {
return (<div className={"footer"}>Footer组件</div>)
}
export default Footer
- layout.js
/**
* 当前组件作用就是将 header 与 footer 显示出来,同时中的 main 内容空出来
* 将来我们传入什么样的 JSX 那么就显示什么样的 DOM
*/
import Header from './Header'
import Footer from './Footer'
function Layout(props) {
return(<>
<Header />
<div className={'main'}>
{props.children}
</div>
<Footer />
</>)
}
export default Layout
- App.js
import Home from './components/Home'
import List from './components/List'
function App() {
return (<List />)
}
export default App;
- style.css
html, body {
height: 100%;
}
body {
margin: 0;
padding: 0;
}
.header, .footer {
width: 100%;
height: 80px;
position: absolute;
left: 0;
line-height: 80px;
text-align: center;
font-size: 18px;
color: #fff;
}
.header {
background-color: skyblue;
top: 0;
}
.footer {
background-color: purple;
bottom: 0;
}
.main {
width: 100%;
position: absolute;
top: 80px;
bottom: 80px;
padding: 20px 0;
text-align: center;
background: khaki;
}
- 案例效果
组件状态
- 组件状态
- 状态就是数据,因此组件状态指的就是某一个组件自己的数据
- 数据驱动 DOM
- 当我们修改某一个数据的时候,界面上的 DOM 中数据展示也会自动更新
- 组件状态管理
import React, {Component} from 'react'
class Header extends Component {
// 在类组件当中默认就存在一个 state 属性,他是一个对象,可以用于保存当前组件的数据
// 还可以通过 setState 方法来修改数据的值,最后修改之后的状态会自动展示在 DOM 上
state = {
name: 'xgd',
age: 18
}
handler = () => {
// 在 react 当中是不能够直接来修改 state 值得
this.setState({
name: 'xxxgd'
})
}
render () {
return (<>
{this.state.name}
{this.state.age}
<button onClick={this.handler}>点击更改</button>
</>)
}
}
export default Header
setState 使用细节
setState 是异步函数
- 使用 async…await 解决
- 调用 setState 的时候可以传入回调函数,在它里边就可以使用修改之后的数据
// 方法1
class Header extends Component {
state = {
name: 'xgd',
age: 18
}
handler = async () => {
await this.setState({
name: 'xxxgd'
})
console.log(this.state.name)
}
...
}
// 方法2
class Header extends Component {
state = {
name: 'xgd',
age: 18
}
handler = () => {
this.setState({
name: 'xxxgd'
}, () => {
console.log(this.state.name, 222)
})
console.log(this.state.name, 111)
}
...
}
setState 在使用的时候除了可以传入对象之外还能够传入一个函数
class Header extends Component {
state = {
count: 0
}
handler = () => {
this.setState((state)=> ({
count: state.count + 1
}))
}
render () {
return (<>
<div>
<span>{this.state.count}</span>
<button onClick={this.handler}>点击更改</button>
</div>
</>)
}
}
setState 在使用的时候既可以传入函数,也可以传入对象,且两者具有不同点的
- 传入对象时,从性能的角度考虑,自己会在内部做出一个合并,所以在使用的时候需要注意
- 传入函数时,会依次执行,推荐使用
组件中的 this
在组件中直接使用 this 会报 undefined,可以通过箭头函数或事件触发时书写箭头函数传值或 bind 方法传值来解决
import React, {Component} from 'react'
class Header extends Component {
handler = function() {
console.log(this);
}
render () {
return (<div>
<span>{this.state.count}</span>
<button onClick={this.handler}>点击更改</button> undefined
<button onClick={() => {this.handler()}}>点击更改</button> 正常
<button onClick={this.handler.bind()this}>点击更改</button> 正常
</div>)
}
}
export default Header
单向数据流
什么是单向数据流
单项数据流的设计原则要求我们将不同组件之间共享的数据都定义在上层
import C1 from './c1'
import React, { Component } from 'react'
class App extends Component {
// 在类组件当中更可以使用 state 定义状态
state = {
name: 'gd',
age: 40
}
render() {
return (<>
<C1 {...this.state}/>
</>)
}
}
export default App;
// C2.js
import React from 'react'
import C2 from './c2'
function C1(props) {
console.log(props)
return (<>
C1 组件
<C2 {...props}/>
</>)
}
export default C1
单向数据流如何修改
import C1 from './c1'
import React, { Component } from 'react'
class App extends Component {
// 在类组件当中更可以使用 state 定义状态
state = {
name: 'gd',
age: 40
}
// 定义状态的更新方法,当前只负责定义,在想要修改数据的地方进行调用
// handler = (target) => {
// this.setState({
// name: target.name,
// age: target.age
// })
// }
// handler 可以通过解构赋值简化操作
handler = ({name, age}) => {
this.setState({ name, age })
}
render() {
return (<>
<C1 {...this.state} change={this.handler}/>
</>)
}
}
export default App;
// C1.js
import React from 'react'
import C2 from './c2'
function C1(props) {
console.log(props)
return (<>
{props.name}
{props.age}
<div>
C1 组件
<C2 {...props}/>
<button onClick={() => {props.change({name: '修改了', age: 188888})}}>点击更改</button>
</div>
</>)
}
export default C1
特点
- 单向数据流,自顶向下,从父到子
- 基于单向数据流动,要求我们将共享的数据定义在上层组件中
- 子组件通过调用父组件传递过来的方法可以更改数据
- 当数据发生更改之后,React 会重新渲染组件树
受控表单
- 受控表单:
- 表单元素的值全部由 React 来进行管理,此时表单元素中的值都放在 state 中,所以表单元素里的值,也需要从 state 当中进行获取
- 非受控表单:
- 不受 react 管理,表单元素的数据由 DOM 元素本身来进行管理,表单元素的值也是存放在 DOM 元素里,获取的时候需要操作 DOM 元素
受控表单绑定与更新
- 将 state 中的状态与表单的 value 值进行绑定 value={this.state.xxxx}
- 如何更新状态值 onChange={方法} ev.target.value
- ev.target.name === [prop]: this.setState({})
- 只绑定了值,但是没有修改方法时提示的警告处理
- readOnly(设置为只读属性)
- defaultValue(只是想要拿到 state 里边的状态)
import React, { Component } from 'react'
class App extends Component {
state = {
name: 'gd',
age: 18
}
// 定义方法用于处理状态里数据值的修改
handler = (ev) => {
let prop = ev.target.name
this.setState({
[prop]: ev.target.value
})
}
render() {
return (<>
<div>
<input name="name" value={this.state.name} onChange={this.handler.bind(this)} />
<input name="age" value={this.state.age} onChange={this.handler.bind(this)} />
<input value={this.state.age} readOnly />
<input defaultValue={this.state.age} />
</div>
</>)
}
}
export default App;
受控表单之下拉菜单
import React, { Component } from 'react'
class App extends Component {
state = {
subject: 'java'
}
render() {
return (<>
<div>
<select value={this.state.subject} onChange={(ev)=>{this.setState({subject: ev.target.value})}}>
<option value='JS'>JS</option>
<option value='java'>java</option>
<option value='python'>python</option>
</select>
<hr></hr>
<button onClick={()=>{console.log(this.state)}}>点击打印</button>
</div>
</>)
}
}
export default App;
受控表单之单选框
import React, { Component } from 'react'
class App extends Component {
state = {
sex: '女'
}
render() {
return (<>
<div>
<input
type="radio"
name="sex"
value="男"
defaultChecked={this.state.sex == '男'}
onChange={(ev)=>{this.setState({sex: ev.target.value})}}
/>男
<input
type="radio"
name="sex"
value="女"
defaultChecked={this.state.sex == '女'}
onChange={(ev)=>{this.setState({sex: ev.target.value})}}
/>女
<button
onClick={()=>{console.log(this.state);}}
>点击更改</button>
</div>
</>)
}
}
export default App;
受控表单之复选框
import React, { Component } from 'react'
class App extends Component {
// 定义状态
state = {
name: 'gd',
age: 18
}
hobbies = [
{
id: 1,
title: 'Vue',
isChecked: true
},
{
id: 2,
title: 'React',
isChecked: false
},
{
id: 3,
title: 'Angular',
isChecked: false
}
]
handler (index, ev) {
this.hobbies[index].isChecked = ev.target.checked
}
submit = (ev)=> {
ev.preventDefault()
let ret = this.hobbies
.filter(hobby => hobby.isChecked)
.map(hobby => hobby.id)
ret = {
...this.state, ret
}
console.log(ret)
}
render() {
return (<>
<form onSubmit={(ev)=>{this.submit(ev)}}>
{
this.hobbies.map((hobby, index) => {
return (
<label key={hobby.id}>
<input type="checkbox" defaultChecked={hobby.isChecked} onChange={this.handler.bind(this, index)} /> {hobby.title}
</label>
)
})
}
<hr />
<button onClick={()=>{console.log(this.hobbies)}}>点击获取</button>
<input type="submit" />
</form>
</>)
}
}
export default App;
非受控表单
import React, { Component } from 'react'
class FormComponent extends Component {
submit = (ev) => {
ev.preventDefault()
// 非受控组件指的就是某一个表单元素的数据,不受 react 管理
// 可以直接从 DOM 元素本身进行获取(获取 DOM 元素,获取具体的值)
console.log(this.refs.useName.value)
}
render() {
return (<form onSubmit={(ev)=>{this.submit(ev)}}>
<input type="text" ref="useName" />
<input type="submit" />
</form>)
}
}
export default FormComponent;