笔记来源:拉勾教育 – 大前端就业集训营
文章内容:学习过程中的笔记、感悟、和经验
React框架基础
体验React
- React是facebook推出的用于构建用户界面的前端js框架
- React使用组件构建用户页面
组件开发的优势
- 将一个庞大、复杂的应用拆分成多个独立单元
- 组件之间相互独立,有利于应用程序的维护
- 组件可以复用,一次编写多地复用
注意:React项目中必须要依赖于react-dom和react-scripts两个包
自定义webpack搭建React环境
-
快速构建一个项目:
npm init --yes
-
书写开发依赖:在package.json中添加书写依赖
"devDependencies": { "@babel/core": "^7.13.8", "@babel/preset-env": "^7.13.9", // 转换 react语法专用的插件 "@babel/preset-react": "^7.12.13", // babel-loader "babel-loader": "^8.2.2", // 限制为 4.xx ,新的 5.xx webpxck里面对应的 html-webpack-plugin 也是 5.xx版本 "html-webpack-plugin": "^4.5.2", // 下面两个是react核心,必须安装 "react": "^17.0.1", "react-dom": "^17.0.1" // webpack 和 webpack-cli 没有用最新的 5.xx版本 "webpack": "^4.46.0", "webpack-cli": "^3.3.12", // 在线运行开发服务器 "webpack-dev-server": "^3.11.2" } 复制代码
-
安装依赖:
npm i
-
书写 webpack 配置文件 – package.config.js
// 引入path内置对象 const path = require('path') // 引入webpack - html插件 const HtmlWebpackPlugin = require('html-webpack-plugin') // webpack配置对象 module.exports = { // 模式 - 开发模式 mode: 'development', // devtool不设置 devtool: 'none', // 入口文件 entry: './src/index.js', // 输出 output: { // 输出文件名 filename: 'main.js', // 输出目录,使用path对象方法 path: path.resolve('dist') }, // 设置本地服务器 devServer: { // 端口 port: 3000, // 热更新 hot: true }, module: { rules: [ { // js 和 jsx 文件的加载器 test: /\.js|jsx$/, // 去除 node_modules 目录 exclude: /node_modules/, use: [ { // 使用 babel loader: 'babel-loader', // 使用的包 options: { presets: ['@babel/preset-env', '@babel/preset-react'] } } ] } ] }, // 插件 plugins: [ // 创建html插件实例 new HtmlWebpackPlugin({ // 模版为 index.html 文件 template: './src/index.html' }) ] } 复制代码
-
新建 src 目录,创建 webpack 入口文件 – index.js 和 Html 模版 – index.html
-
在html模版中快速创建结构并且添加一个 id 为 root 的元素
<body> <!-- 作为React渲染的根元素 --> <div id="root"></div> </body> 复制代码
-
书写入口文件index.js
// 引入react,注意这里引入后要使用首字母大写的 React import React from 'react' // 引入react-dom 中的 render 方法 import { render } from 'react-dom' // 创建函数模版 function App() { // 返回结构 return <div>Hello React</div> } // 使用render方法进行渲染 // 参数1:渲染的模版 // 参数2:渲染根元素 render(<App />, document.getElementById('root')) 复制代码
-
运行webpack-dev-server查看项目:
npx webpack-dev-server
结论:React本质上也需要使用webpack进行打包处理,即使后续我们使用脚手架创建react项目,其底层也集成了webpack
脚手架创建项目并初始化
-
全局安装脚手架工具:
(sudo)npm install -g create-react-app
-
创建项目:
create-react-app 项目名
,程序会自动帮助我们创建项目并进行相关设置形成一个新的React项目 -
初始化项目:删除我们不需要的文件
- public目录下只留下 index.html 文件
- src目录下只留下 app.js 和 index.js 文件
- 其他项目无需修改
-
修改剩文件中的import、src和内容
public/index.html中删除多余的引入文件和注释,只留下下面内容 <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="theme-color" content="#000000" /> <meta name="description" content="Web site created using create-react-app" /> <title>React App</title> </head> <body> <div id="root"></div> </body> </html> 复制代码
// src/App.js中删除多余的inport,简化app模版内容,只留下下面内容 function App() { return ( <div>Hello React</div> ) } export default App 复制代码
src/index.js中删除多余的inport,只留下下面内容 import React from 'react' import ReactDOM from 'react-dom' import App from './App' ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById('root') ) 复制代码
-
运行项目:
npm / yarn start
,老师使用的是npm,react推荐的是yarn
JSX语法
JSX可以看作是js语言的扩展,它既不是一段字符串,也不是一个HTML
JSX具备js的所有功能,同时可以被转为HTML在界面上进行展示(react、react-dom)
JSX的一些功能
- 动态显示数据,使用{ }
- 调用方法
- 支持表达式,不支持if语句(支持三元表达式)
- 支持模版字符串
- 注意:不能直接使用对象
// 创建实验用的变量
const name = '拉勾教育'
function sayHi() {
return 'Hi 拉勾教育'
}
const a = false
const obj = {
name: '拉勾教育',
age: 100
}
function App() {
return (
// 注意:和Vue一样只能有一个容器标签
<div>
{/* 直接调用变量无效 */}
<p>name</p>
{/* 想使用变量必须在{}中使用 */}
<p>{name}</p>
{/* 使用函数 */}
<p>{sayHi()}</p>
{/* 使用三元表达式 */}
<p>{a ? 1 : 2}</p>
{/* 使用模版字符串 */}
<p>{`hello ${name}`}</p>
{/* 调用js内置方法 */}
<p>{console.log('打印')}</p>
{/* 对象需要转字符串之后词啊能使用 */}
<p>{JSON.stringify(obj)}</p>
{/* 写注释 */}
<p>{/* 注释要这么写 */}</p>
</div>
)
}
export default App
复制代码
上面函数最终return使用的就是JSX,可以直接当做HTML使用
JSX特点
- JSX本身就是一个表达式,所以可以作为变量赋值、函数参数 / 返回值等等
- JSX可以添加属性(使用双引号包裹)、动态属性
- JSX可以添加子元素
- JSX(return返回)只能存在一个顶层元素(类似Vue组件)
- 使用但标签必须书写关闭
// JSX作为表达式const a = <p>作为表达式</p>const b = 123function App() { return ( // jsx作为了函数的返回值 <div> {a} <p title="添加属性">添加属性</p> <p value={b}>添加动态属性</p> <img /> </div> )}export default App
复制代码
建议return后面使用()返回js,便于代码管理,不加也没有什么问题
JSX事件处理
-
事件绑定
-
直接使用驼峰命名法在元素生添加属性即可
const clickBtn = () => { console.log('事件执行了')}function App() { return ( <div> <button onClick={clickBtn}>点击触发事件</button> </div> )} 复制代码
-
-
事件传参
-
利用箭头函数,在箭头函数内调用传参即可
-
使用bind返回一个新的函数,在新的函数内传参
const clickBtn = (a, b) => { console.log(a, b) console.log('事件执行了')}function App() { return ( <div> {/* 使用箭头函数 */} <button onClick={() => {clickBtn(1, 2)}}>点击触发事件</button> {/* 使用bind */} <button onClick={clickBtn.bind(null, 1, 2)}>点击触发事件</button> </div> )} 复制代码
-
-
获取事件对象
-
默认情况下不接收参数,默认第一个形参就是事件对象本身
-
使用箭头函数传参,在箭头函数传递参数,即为事件对象本身
-
使用bind传递参数,最后一个参数即为事件对象本身
-
使用bind不传参和第一种一样
// 三个事件函数const clickBtn = (e) => { console.log(e) console.log('事件1执行了')}const clickBtn2 = (a, b, e) => { console.log(e) console.log('事件2执行了')}const clickBtn3 = (a, b, e) => { console.log(e) console.log('事件3执行了')}function App() { return ( <div> {/* 默认不传参,第一个参数就是事件对象本身 */} <button onClick={clickBtn}>点击触发事件</button> {/* 使用箭头函数传参,事件对象在箭头函数参数中获取 */} <button onClick={(e) => {clickBtn2(1, 2, e)}}>点击触发事件2</button> {/* 使用bind,事件参数为形参的最后一个 */} <button onClick={clickBtn3.bind(null, 1, 2)}>点击触发事件3</button> </div> )} 复制代码
-
循环数据遍历
JSX一个特点:直接使用数组会自动解构并且遍历数组内容
const arr = [<p>1</p>, <p>2</p>, <p>3</p>]function App() { return ( <div> // jsx会将数组中的每一项解构出来,然后使用,因为我这里每一项都是标签,所以渲染出来直接就是三个p标签 {arr} </div> )}
复制代码
利用这个特点进行复杂数据的遍历
// 创建一个数组,每一项都是jsx不能处理的对象结构const arr = [ { id: 0, name: 'zs', age: 18, sex: 0 }, { id: 1, name: 'hmm', age: 20, sex: 1 }]function App() { // 利用map方法对数组进行遍历,将jsx返回li组合成新数组 const newArr = arr.map(item => { return ( // 这里需要使用key做标记,与Vue同理 <li key={item.id}> <p>{item.name}</p> <p>{item.age}</p> <p>{item.sex}</p> </li> ) }) return ( <div> {/* 使用新的数组,jsx会自动解构 */} <ul>{newArr}</ul> </div> )}
复制代码
添加内联 / 行内样式
添加内联样式支持4种方式
-
直接添加内联样式
function App() { return ( <div> {/* 直接使用内联样式进行添加 单独写在一个{}内部,不需要写单位、使用驼峰命名法、 */} <p style={{width: 100, height: 100, backgroundColor: 'pink'}}></p> </div> )} 复制代码
-
将内联样式单独封装为一个对象引入
// 样式对象const style = { width: 100, height: 100, backgroundColor: 'pink'}function App() { return ( <div> {/* 单独封装样式对象直接引用即可 */} <p style={style}></p> </div> )}export default App 复制代码
-
默认不支持伪类和媒体查询:需要使用radium包实现支持
安装依赖:
npm install --save radium
src/App.js// 引入radiumimport Radium from 'radium'// 样式对象const style = { width: 100, height: 100, backgroundColor: 'pink', // 伪类 - 鼠标以上 ":hover": {backgroundColor: 'skyblue'}, // 媒体查询,视窗宽度小于500宽度为200px(媒体查询还需要再使用app的组件中进行设置一下) "@media (max-width: 500px)": {width: 200}}function App() { return ( <div> {/* 单独封装样式对象直接引用即可 */} <p style={style}></p> </div> )}// 使用radium包裹住再进行导出就可以支持了export default Radium(App) 复制代码
// src/index.js 再index.js入口文件中使用radiumimport React from 'react'import ReactDOM from 'react-dom'import App from './App'// 引入radium中的StyleRoot方法import {StyleRoot} from 'radium'ReactDOM.render( <React.StrictMode> {/* 使用StyleRoot包裹app就可以使用媒体查询了 */} <StyleRoot><App /></StyleRoot> </React.StrictMode>, document.getElementById('root') 复制代码
-
radium提供多样式添加
// 引入radiumimport Radium from 'radium'const button = { // 内部书写多个样式 base: { width: 100, height: 100, backgroundColor: '#fff' }, login: { backgroundColor: 'green' }, logout: { backgroundColor: 'red' }}// 信号值const login = falsefunction App() { return ( <div> {/* style内联样式月可以书写成数组形式 */} <button style={[ // 第一个样式为公共样式 button.base, // 第二个样式根据信号值进行判断 login ? button.login : button.logout ]}></button> </div> )}// 使用radium包裹住再进行导出就可以支持了export default Radium(App) 复制代码
注意:后期使用内联样式比较少,大多数情况下还是使用外链样式
外联样式
使用外链样式有以下三种方案
-
全局外链样式(所有组件都可以使用)
/* src/style.css 新建一个全局的样式css文件 */.box { width: 100px; height: 100px; background: pink;} 复制代码
// src/index.js 入口文件中直接引入并使用这个全局样式文件import React from 'react'import ReactDOM from 'react-dom'import App from './App'import './style.css'ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById('root')) 复制代码
// src/App.js 在组件中使用即可,会自动获取样式function App() { return ( <div> {/* 这里要使用className设置类名,两种写法,我更倾向第一种,老师用的第二种 */} <p className="box"></p> <p className={'box'}></p> </div> )}export default App 复制代码
-
局部外链样式(单组件使用)
/* src/App.module.css 新建一个和组件名相关的css文件(老师推荐组件名+module.css) */.box { width: 100px; height: 100px; background: pink;} 复制代码
// src/App.js 想应组件中引入样式文件,注意这里需要引入为一个对象import style from './App.module.css'function App() { return ( <div> {/* 使用的时候要解构这个对象 */} <p className={style.box}></p> </div> )}export default App 复制代码
-
CSS In JS:使用styled-components包,组件较小可以考虑使用
安装styled-components:
npm install --save styled-components
// src/App.js 组件中使用styled-components在js内部书写css// 引入 styled-componentsimport styled from 'styled-components'// 利用styled-components创建标签// styled.div表示创建一个div标签const Box = styled.div.attrs({ // attrs可以添加标签属性 className: 'box', title: '我是styled创建的'})` width: 100px; height: 100px; background: pink;`// styled.div最后跟上模版字符串可以在内部书写样式function App() { return ( <div> {/* 最后把新建的styled-components当作组件使用即可 */} <Box /> </div> )}export default App 复制代码
组件创建方式
创建组件有两种方式(使用方法都相同):
-
函数组件:将组件通过函数return返回
// 组件名即函数名function App() { // 使用return返回组件内容即可 return ( <div> 内容 </div> )}export default App 复制代码
-
类组件:将组件通过类继承的方式创建
// src/about.js 新建一个组件// 引入react 和 Componentimport React, {Component} from 'react'// 使用类继承的方式创建类组件class About extends Component { // 使用render方法创建组件 render() { return ( // 返回组件内容 <p>About组件内容</p> ) }}// 导出组件export default About 复制代码
如果不想在组件外面使用顶级标签可以使用Fragment
或者语法糖<>组件内容</>
的方式避免过多顶层标签的出现,个人偏向语法糖写法
// Fragment需要引入import React, {Component, Fragment} from 'react'return ( // 返回组件内容 <Fragment>About组件内容</Fragment>)// 语法糖直接不写顶层标签名即可return ( // 返回组件内容 <>About组件内容</>)
复制代码
向组件传递数据
函数组件父向子传值
{/* 父组件直接使用属性传值 */}<Header name="拉勾教育" age="18" />
复制代码
// src/header.js 子组件// 子组件通过函数的参数props接收全部父组件传递过来的值function Header(props) { return ( <div> <p>Header 组件内容</p> {/* 直接使用props的属性即可 */} <p>{props.name}</p> <p>{props.age}</p> </div> )}export default Header
复制代码
可以将要传递的数据统一管理在一个对象中,然后通过解构方式传递,更简洁
// 把要传递的数据统一管理const data = { name: "拉勾教育", age: "18"}function App() { return ( <div> {/* 通过ES6的语法打开数据 */} <Header {...data} /> </div> )}
复制代码
同样接收数据的时候也可以做一个解构处理
// 参数部分直接解构传递过来的数据function Header({name, age}) { return ( <div> <p>Header 组件内容</p> {/* 直接使用结构后的数据或者还按照之前的写法 */} <p>{name}</p> <p>{age}</p> </div> )}export default Header
复制代码
类组件父向子传值
{/* 父组件直接使用属性传值,和函数组件相同 */}<Header name="拉勾教育" age="18" />
复制代码
import React, {Component} from 'react'class About extends Component { render() { // 类组件内部会有一个props属性可以获取到全部数据 console.log(this.props) return ( <> {/* 直接使用即可 */} <p>{this.props.name}</p> <p>{this.props.age}</p> </> ) }}export default About
复制代码
同样可以把所有数据统一管理到一个对象中
// 把要传递的数据统一管理const data = { name: "拉勾教育", age: "18"}function App() { return ( <div> {/* 通过ES6的语法打开数据 */} <About {...data} /> </div> )}
复制代码
同样可以通过解构来处理
import React, {Component} from 'react'class About extends Component { render() { const {name, age} = this.props return ( <> {/* 直接使用即可 */} <p>{name}</p> <p>{age}</p> </> ) }}export default About
复制代码
个人偏向于
- 使用对象统一管理数据
- 通过函数组件的方式创建组件
- 通过ES6打开对象的方式传值
- 通过解构的方式解构数据直接使用
Props默认值设置
函数组件设置props默认值
直接使用组件名.defaultProps = {参数名:默认值........}
设置
function Header(props) { const {name, age} = props return ( <div> <p>Header 组件内容</p> {/* */} <p>{name}</p> <p>{age}</p> </div> )}// 直接设置默认值!!!!!!!!!!!!!!!!!!!!Header.defaultProps = { name: '111', age: 111}export default Header
复制代码
类组件设置props默认值
在类内部设置静态属性defaultProps即可
import React, {Component} from 'react'class About extends Component { // 直接设置静态属性即可设置默认值!!!!!!!!!!!!!!!!! static defaultProps = { name: '111', age: 111 } render() { const {name, age} = this.props return ( <> <p>{name}</p> <p>{age}</p> </> ) }}export default About
复制代码
props类型校验
类型校验需要使用第三方包prop-types进行处理
安装依赖:npm i prop-types --save
,中文文档
// 引入依赖获得校验功能import PropTypes from 'prop-types'function Header(props) { const {name, age} = props return ( <div> <p>Header 组件内容</p> {/* */} <p>{name}</p> <p>{age}</p> </div> )}Header.defaultProps = { name: '111', age: 111}// 进行数据校验// 注意这里的 Header.propTypes和下面的 PropTypes不是同一个Header.propTypes = { // PropTypes.string:字符串类型 // isRequired:必须 name: PropTypes.string.isRequired, age: PropTypes.number}export default Header
复制代码
老师没有演示类组件的校验方法
向组件传递JSX
当父组件向子组件传递了JSX语法结构,那么需要在组件中使用props.children获取
// 引入新建的组件import About from './about'import Header from './header'// 把要传递的数据统一管理const data = { name: "拉勾教育", age: "18"}function App() { return ( <div> {/* 向两个子组件传递JSX!!!!!!!!!!!!!!! */} <About> <p>App向About传递的JSX</p> <p>App向About传递的JSX</p> </About> <Header> <p>App向About传递的JSX</p> </Header> </div> )}export default App
复制代码
// 函数组件可以直接从参数props中拿到children获得JSXfunction Header(props) { return ( <div> <p>Header 组件内容</p> <p>{props.name}</p> <p>{props.age}</p> {/* 拿JSX!!!!!!!!!!!!!!!!!! */} {props.children} </div> )}
复制代码
// 类组件也可以从this.props.children中拿到JSXclass About extends Component { static defaultProps = { name: '111', age: 111 } render() { return ( <> <p>{this.props.name}</p> <p>{this.props.age}</p> {/* 拿JSX!!!!!!!!!!!!!!!!!! */} {this.props.children} </> ) }}
复制代码
JSX可以不止一行,可以是多行多个标签
组件布局实例制作
- 实现效果如图
- 需要制作的组件:Header组件、home组件、List组件、Footer组件
- 可以考虑将Header和Footer组件进行封装,然后home和List引入封装后的组件通过传入不同的JSX实现不同的内容
- 组件统一放到新的目录components中
// src/components/header.js 创建Header组件,添加类名function Header() { return ( <> <p className="header">Header组件内容</p> </> )}export default Header// src/components/footer.js 创建Footer组件,添加类名function Footer() { return ( <> <p className="footer">Footer组件内容</p> </> )}export default Footer
复制代码
// src/components/layout.js 创建Layout封装Footer和Header组件// 引入两个公共组件import Header from './header'import Footer from './footer'// 接收参数function Layout(props) { return ( <> <Header /> {/* 使用父组件传进来的JSX */} {props.children} <Footer /> </> )}export default Layout
复制代码
// src/components/home.js 新建Home组件并引入子组件Layout,传入要显示的JSX// 引入封装的Layout组件import Layout from './layout'function Home() { return ( <Layout> {/* 向Layout传递Jsx实现内容的定制 */} <p className="main">Home组件内容</p> </Layout> )}export default Home// src/components/list.js 新建List组件并引入子组件Layout,传入要显示的JSXimport Layout from './layout'function List() { return ( <Layout> {/* 向Layout传递Jsx实现内容的定制 */} <p className="main">List组件内容</p> </Layout> )}export default List
复制代码
// 在app.js中使用最后封装的组件// 引入两个子组件Hom和List,两者都封装了Header和Footerimport Home from './components/home'import List from './components/list'function App() { return ( <div> {/* 使用组件 */} <List /> </div> )}export default App
复制代码
/* src/style.css 新建css文件添加样式 */* { padding: 0; margin: 0;}.header { width: 100%; height: 100px; background: skyblue;}.footer { position: fixed; bottom: 0; width: 100%; height: 100px; background: #891189;}.main { width: 100%; height: calc(100vh - 200px); background: #f2e79d;}
复制代码
// src/index.js 在index.import React from 'react'import ReactDOM from 'react-dom'import App from './App'// 直接使用刚刚编辑的样式文件作为全局样式import './style.css'ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById('root'))
复制代码
总结
布局设置的两种思路
- 看到什么写什么,都封装为单独组件,然后引入页面
- 将经常复用的组件抽出来单独封装
组件状态
注意:(默认没有钩子的情况下)函数组件无法保存状态,是无状态组件,如果想保存状态需要修改为类组件
组件状态 :组件内部自己保存的数据
- 我们将组件自己的状态(数据)让他自己管理
- 类数组中有一个state属性可用于管理数据
- 可以使用this.setState方法对数据进行操作,注意这里直接修改this.state无效
// 引入组件,不需要引入react这里省略了import {Component} from 'react'// 创建类组件class Header extends Component { // 存储数据 state = { name: 'zs', age: 10 } // 按钮点击方法,这里使用箭头函数,否则会报错 clickbtn = () => { // 调用setState方法进行数据的修改,注意不能直接修改 this.setState({ age: 20 }) } render() { // Jsx return ( <> {/* 点击按钮修改数据 */} <button onClick={this.clickbtn}>点击</button> <p>{this.state.name}</p> <p>{this.state.age}</p> </> ) }}export default Header
复制代码
setState注意事项
-
setState是异步函数
- 可以使用 sync – await 进行同步处理
- setState支持传入第二个参数,为一个回调函数,在回调函数内部操作
// 注意点:setState是异步函数,后面的代码可能会先执行// clickbtn = () => {// this.setState({// age: 20// })// console.log(this.state.age) //返回10// }// 解决方案1:使用 async - await 进行同步化处理,让后面的代码等待// clickbtn = async () => {// await this.setState({// age: 20// })// console.log(this.state.age) //返回20// }// 解决方案2:使用setStatr的第二个参数,支持传入一个函数,函数会等待前面运行完之后再运行,相当于then方法clickbtn = () => { this.setState({ age: 20 }, () => console.log(this.state.age)) //返回20} 复制代码
-
setState第一个参数可以传入一个函数
import {Component} from 'react'class Header extends Component { state = { num: 0 } // 常规第一个参数为对象 // add = () => { // this.setState({ // num: this.state.num + 1 // }) // } // setState第一个参数也可以是一个函数,函数最终返回和上面一样的对象 add = () => { this.setState((state) => ({ num: state.num + 1 })) } render() { // 下面是一个计数器功能 return ( <> {this.state.num} {/* 点击按钮实现数字+1 */} <button onClick={this.add}>+1</button> </> ) }}export default Header 复制代码
-
setState如果第一个函数传入的是函数,那么多个setState都可以触发,反之如果传入的是一个对象,只有最后一个setState可以触发
// 如果第一个参数使用函数,那么多个setState都可以触发add = () => { this.setState((state) => ({ num: state.num + 1 })) this.setState((state) => ({ num: state.num + 5 })) // 最终点击一次+6}// 如果第一个参数是对象,只有最后一个setState可以触发add = () => { this.setState({ num: this.state.num + 1 }) this.setState({ num: this.state.num + 5 }) // 最终点击一次只能+5} 复制代码
推荐使用函数
组件中的this
组件函数中,如果使用function声明函数的时候,this指向undefined
add = function () { console.log(this) //this指向undefined }
复制代码
解决方法:
-
函数定义使用箭头函数
add = () => { console.log(this) //this指向组件} 复制代码
-
函数调用的时候直接使用箭头函数调用
// 使用箭头函数调用函数<button onClick={() =>{this.add()}}>+1</button>add = function () { console.log(this) //this指向undefined} 复制代码
-
利用bind进行绑定
// 利用bind绑定<button onClick={this.add.bind(this)}>+1</button>add = function () { console.log(this) //this指向undefined} 复制代码
单向数据流动
- 单向数据流动:即数据可以根据组件的嵌套关系,自顶而下的传递数据,而每一层组件只需要将接收到的数据传递下去即可
- 单向数据流修改数据:单向数据流中子组件可以使用父组件传递过来的方法来修改数据(前提是父组件传递过来的数据中有相应的方法)
- 特点:在单向数据流中,一旦某一条数据发生变化,整个流动路径都会发生数据更新重新渲染
// src/App.js 单向数据流源头// 因为 App 要保存状态(数据),所以需要设置为类组件// 引入组件import {Component} from 'react'// 引入C1组件import C1 from './C1'// App组件class App extends Component { // 状态数据 state = { name: 'cwn', age: 28 } // 修改状态数据的方法,这里利用了ES6的语法对参数进行了结构 chageState = ({name, age}) => { // 这里利用ES6新语法 - 状态名和值相同可简写 this.setState({name,age}) } render() { return ( <div> {/* 把数据和修改方法传递给C1组件 */} <C1 {...this.state} chageState={this.chageState}/> </div> ) }}// 导出export default App
复制代码
// src/C1.js 单向数据流第一次流动// 引入C2组件import C2 from './C2'// C1组件,注意形参function C1(props) { // 打印父组件传递过来的数据看一下 console.log(props) return ( <> <p>C1组件</p> {/* 将父组件传递过来的参数继续向下传递 */} <C2 {...props}/> </> )}// 导出export default C1
复制代码
// src/C2.js 单向数据流第二次流动// 引入C3组件import C3 from './C3'// C2组件function C2(props) { // 打印父组件传递过来的数据看一下 console.log(props) return ( <> <p>C2组件</p> {/* 将父组件传递过来的参数继续向下传递 */} <C3 {...props}/> </> )}// 导出export default C2
复制代码
// src/C3.js 单向数据流最后一次流动,这里进行数据修改操作function C3(props) { // 打印父组件传递过来的数据看一下 console.log(props) return ( <> <p>C3组件</p> {/* 按钮,点击后触发App一路传递过来的数据中的 chageState 方法修改数据 */} <button onClick={() => props.chageState({name: 'ccc', age: 20})}>修改数据</button> {/* 注意这里写成箭头函数,否则会出问题 */} </> )}export default C3// 单向数据流动:App -> C1 -> C2 -> C3// C3实现修改数据:App中定义修改数据的方法 -> 方法随着数据流一路向下 -> 最终C3调用这个方法并传递数据// 单向流动特点:当C3修改了数据后。C1、C2、C3中的 “console.log(props)” 又被触发了一次,说明修改单向流数据会导致整个流动路径重新渲染// 好处:单向流便于管理,总能找到源头,便于查找错误
复制代码
受控表单 – 绑定与更新
- 表单分类
- 受控表单:表单的值全部由React管理,此时React把数据放置在state中,表单通过this.state获取
- 非受控表单(不常用):值不由React管理,全部由DOM自己管理,获取需要操作DOM元素
- 受控表单的数据绑定和修改
- 受控表单数据通过state进行获取
- 想要更新值可以使用input的onChage事件,此时需要传递事件对象
- 修改值可以采用两种方式
- 直接在DOM中书写事件函数
- 单独书写事件函数,触发事件的时候调用(此时需要获取需要更新的值的名字)
- 细节:如果我们input标签不写onChage事件可能会报错,可以使用readOnluy或者defaultValue处理
// 引入组件import {Component} from 'react'// App组件class App extends Component { // 状态数据 state = { name: 'cwn', age: 28, num: 0 } // 更改数据方法,e为事件对象 chagedData = (e) => { // 调用 setState 修改数据 this.setState({ // 这里使用动态属性名获取DOm元素name属性,属性值也是动态的 [e.target.name]: e.target.value }) } render() { return ( <div> {/* 修改数据方法1:直接写在Dom元素内部(语法较短可以考虑) */} <input value={this.state.name} onChange={(e) => this.setState({name: e.target.value})}/> {/* 修改数据方法2:事件调用方法,然后把事件对象传递过去 */} <input name="name" value={this.state.name} onChange={(e) => this.chagedData(e)}/> <input name="age" value={this.state.age} onChange={(e) => this.chagedData(e)}/> {/* 如果不写onChange的话React会提示错误,使用 readOnly 或者 defaultValue 可以处理 */} <input name="age" value={this.state.age} readOnly/> <input name="age" defaultValue={this.state.age}/> </div> ) }}// 导出export default App
复制代码
受控表单 – 下拉菜单
下拉菜单和上面类似,同样需要定义初始状态,然后修改状态的时候也要使用onchange事件处理
// 引入组件import {Component} from 'react'// App组件class App extends Component { // 状态数据 state = { value: 2 } render() { return ( <div> {/* 下拉菜单,value获取状态数据,使用事件进行状态数据修改,e为事件对象 */} <select value={this.state.value} onChange={(e) => this.setState({value: e.target.value})}> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> <option value="4">4</option> </select> {/* 随之展示当前value值 */} {this.state.value} </div> ) }}// 导出export default App
复制代码
受控表单 – 单选框
单选框和上面也几乎相同,需要注意的是
- 两个单选框name要相同才能归结为一组
- 选中状态使用defaultChecked设置
// 引入组件import {Component} from 'react'// App组件class App extends Component { // 状态数据 state = { sex: true } render() { return ( <div> {/* 一组单选框,设置相同的name、选中状态依赖于 state.sex 属性,修改时修改状态数据 */} <input type='radio' name='sex' defaultChecked={this.state.sex} onChange={() => this.setState({sex: true})} />男 <input type='radio' name='sex' defaultChecked={!this.state.sex} onChange={() => this.setState({sex: false})} />女 {/* 实时展示当前sex值 */} <p>{this.state.sex ? '男' : '女'}</p> </div> ) }}// 导出export default App
复制代码
受控表单 – 复选框
- 复选框可以单独书写,这里会涉及到遍历的问题
- render里面可以书写js语句,复选框可以使用循环语句创建
// 引入组件import {Component} from 'react'// App组件class App extends Component { // state状态数据 state = { name: 'cwn', age: 18 } // 复选框数据 option = [ {id: 0, title: 'Vue', select: true}, {id: 1, title: 'React', select: false}, {id: 2, title: 'JS', select: false} ] // 复选框点击事件 // index - 数组索引(因为数组id和索引相同就用ID代替了) // ev - 事件对象 chageCheckbox = (index, ev) => { // 修改数据 this.option[index].select = ev.target.checked } // 表单提交事件 submit = (ev) => { // 仅用默认行为 ev.preventDefault() // 获取全部 select 为 true 的 id值(先筛选全部选中的复选框,再从复选框中获取相应id值) let ret = this.option.filter(item => item.select).map(item => item.id) // 将要发送的数据进行合并 ret = {...this.state, ret} // 最后提交(这里暂时不能提交先打印) console.log(ret) } render() { return ( // form表单,添加表单提交的方法 <form onSubmit={this.submit}> {/* 遍历数组 */} {this.option.map(item => { return ( // 创建结构,key为标记,不写可能会报错 <label key={item.id}> {/* 复选框,调用数据,添加onchange事件 */} <input type="checkbox" defaultChecked={item.select} onChange={this.chageCheckbox.bind(this, item.id)} />{item.title} </label> ) })} <hr/> {/* 在下面实时显示三个复选框选中状态 */} {this.option.map(item => { return( <span key={item.id}>{item.select ? '选中 ' : '未选中 '}</span> ) })} {/* 提交按钮 */} <input type="submit" value="提交" /> </form> ) }}// 导出export default App
复制代码
非受控表单
- 非受控表单即表单元素内部数据不被React管理,无法直接通过React获取访问
- 所以非受控表单数据想要获取只能先获取到表单元素,才能获取内部数据
// 引入组件import {Component} from 'react'// App组件class App extends Component { // 表单提交事件 submit = (ev) => { // 禁用默认行为 ev.preventDefault() // 打印非受控表单数据 console.log(this.refs.user.value) } render() { return ( <form onSubmit={this.submit}> {/* 非受控表单,添加ref属性方便后面获取Dom元素 */} <input type='text' ref='user' /> {/* 提交按钮 */} <input type='submit' /> </form> ) }}// 导出export default App// 这里可能会报错,是因为当前React采取了严格模式导致的,取消严格模式(idex.js的<React.StrictMode>)即可解决// 和面还会使用其他方法获取元素
复制代码