# React **Repository Path**: showlotus/react ## Basic Information - **Project Name**: React - **Description**: No description available - **Primary Language**: JavaScript - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2021-05-08 - **Last Updated**: 2021-05-29 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 我的React学习之路 ## 在HTML中运用 - 引入JS文件 ```html ``` - JSX语法规则 1. 定义虚拟DOM不需要引号 2. 用 `{}` 包裹 `JS表达式` `(!不是语句!)` - 表达式 ```js let a let a = b + c console.log(1) arr.map() function fn(){} ... ``` - 语句 ```js if(){} for(){} switch(){} ... ``` 3. 用 `className` 替换 `class` 4. 内联样式用 `style={{key: value}}` 5. 必须只有一个根标签 6. 标签必须闭合 7. 标签首字母 (1). 若小写字母开头,则识别为html标签,html没有该标签则报错 (2). 若大写字母开头,则识别为组件,没有该组件则报错 - 事件绑定 - 方法1 ```js document.getElementById('btn').onclick = function(){} ``` - 方法2 ```js document.getElementById('btn').addEventListener('click', function(){}) ``` - 方法3(推荐使用) ```html ``` ```js // js function pop(){} ``` - 组件 - 函数式组件(只能使用类式组件的props) ```jsx function Person(props) { const { name, age, gender } = props return ( ) } const p = { name: "Tom", age: 30, gender: 'female', speak: speak } ReactDOM.render(, document.getElementById('main')) const speak = () => { } ``` - 类式组件 ```jsx class Person extends React.Component { // 对标签属性值进行约束 static propTypes = { name: PropTypes.string.isRequired, age: PropTypes.number, gender: PropTypes.string, speak: PropTypes.func } // 对标签属性值设置默认值 static defaultProps = { gender: '不告诉你', age: 18 } render() { const { name, age, gender } = this.props return ( ) } } ReactDOM.render(, document.getElementById('main')) ``` 三大核心属性 - state ```jsx class Weather extends React.Component { state = { isHot: false, temperature: 20 } render() { const { isHot } = this.state return } changeWeather = () => { const { isHot } = this.state this.setState({ isHot: !isHot }) console.log(this.state) } } ``` - props ```jsx class Person extends React.Component { // 对标签属性值进行约束 static propTypes = { name: PropTypes.string.isRequired, age: PropTypes.number, gender: PropTypes.string, speak: PropTypes.func } // 对标签属性值设置默认值 static defaultProps = { gender: '不告诉你', age: 18 } render() { const { name, age, gender } = this.props return ( ) } } const p = { name: "Tom", age: 30, gender: 'female', speak: speak } ReactDOM.render(, document.getElementById('main')) // => ReactDOM.render(, document.getElementById('main')) ``` - ref ```jsx class Com extends React.Component { /* React.createRef 调用后返回一个容器,该容器返回可以存储ref所标识的节点 该容器只能唯一使用,专人专用 */ myRef = React.createRef() showLeftInputData = () => { const input1 = document.getElementById('input1') console.log(this.refs.btn) } showRightInputData = () => { console.log(this.refs.ipt2.value) } showData = () => { console.log(this.fn_input1.value) } showData2 = () => { console.log(this.fn_input2.value) } showData3 = () => { console.log(this.myRef.current.value) } setRef = (c) => { this.fn_input2 = c } render() { return (

字符串形式ref

   

回调函数形式ref(直接绑定在组件的this上)

若以内联函数形式,则在更新状态时会触发两次(无关紧要)
this.fn_input1 = c} onBlur={this.showData} type="text" placeholder="回调函数形式ref" />
内绑定回调函数

ref

) } } ``` - 受控/非受控组件 - 非受控组件(使用到了ref,不建议使用) ```jsx class Box extends React.Component { render() { return (
this.input = c} type="text" name="username" />
) } } ``` - 受控组件 ```jsx class Box extends React.Component { render() { return (

) } } ``` - 生命周期(旧) 1. 初始化阶段:由 `ReactDOM.render()` 触发 --- 初次渲染 1. `constructor()` 2. `componentWillMount()` 3. `render()` 4. **`componentDidMount()`**:开启定时器、发送网络请求、订阅消息 2. 更新阶段:由组件内部 `this.setState()` 或父组件 `render` 触发 1. `shouldComponentUpdate()` 2. `componentWillUpdate()` 3. **`render`**:必备 4. `componentDidUpdate` 3. 卸载组件:由 `ReactDOM.unmountComponentAtNode()` 触发 - **`componentWillUnmount()`**:关闭定时器、取消订阅消息 - 生命周期(新) - 三个钩子函数需要添加 `UNSAFE_` 前缀,可能以后要废弃 1. `componentWillMount` 2. `componentWillReceiveProps` 3. `componentWillUpdate` - 执行顺序 1. `constructor()` 2. `componentDidMount()` 3. **`getDerivedStateFromProps()`** ```jsx /* 得到一个派生的状态 适用于state的值在任何时候都取决于props 将从props传来的属性值,派生给state中的属性 派生状态会导致代码冗余,组件难以维护 */ static getDerivedStateFromProps(props, state) { /* props 组件传递过来的属性 state 原件原生状态中的属性 可以对两者进行校验 */ console.log('derived state', props, state) return null // return { count: -1 } /* 返回一个对象,用该对象对state中相同的属性进行覆盖 覆盖掉的属性无法修改和触发更新 */ } ``` 4. `shouldComponentUpdate()` 5. `render()` 6. **`getSnapshotBeforeUpdate()`** ```jsx // 更新前的回调函数,并将返回值作为 DidUpdate() 的参数传递 getSnapshotBeforeUpdate() { return 1 } componentDidUpdate(preProps, preState, val) { console.log(val) // 1 } ``` 7. `componentDidUpdate()` 8. `componentWillUnmount()` - Diff算法 - 脚手架 - 项目创建 1. 先全局安装脚手架 ```shell npm i -g create-react-app ``` 2. 切换到项目目录 ```shell create-react-app XXX ``` 3. 创建后的项目文件目录 ``` ├── public 静态资源文件 │ ├── index.html 主页面 │ ├── manifest.json 应用加壳配置 │ └── robots.txt 爬虫配置 └── src ├── App.css ├── App.js ├── App.test.js 测试 ├── index.css 通用样式 ├── index.js 入口文件 ├── reportWebVitals.js 记录页面性能 └── setupTests.js 组件测试 ``` - 代理转发 1. 配置 `package.json`:只适用于单一服务器代理 ```json { { }, "proxy": "http://127.0.0.1:9000" // 目标服务器地址 } ``` 请求样例 ```js const url = `http://127.0.0.1:3000/test` // 要请求的地址改为当前开发环境的模拟服务器地址 // 当请求的路径文件在开发环境中存在时,则不会像服务器发起请求 // 而是请求开发环境的本地资源文件 axios.get(url).then( res => { console.log('success', res.data) }, err => { console.log(err) }) ``` 2. 配置 `setupProxy.js`:适用于多个服务器代理 ```js // commonJS 语法 const proxy = require("http-proxy-middleware") module.exports = function (app) { app.use( // 将请求API中包含 /api1 字段的请求代理转发到 target 的地址 proxy('/api1', { target: 'http://localhost:9000', // 请求转发给谁 changeOrigin: true, // 控制服务器收到的请求头中Host字段的值 pathRewrite: { // 重写请求路径 '^/api1': '' } }), proxy('/api2', { target: 'http://localhost:9001', changeOrigin: true, pathRewrite: { '^/api2': '' } }), ) } ``` - 消息订阅-发布机制 `pubsub-js` ```js // 引入依赖 import PubSub from 'pubsub-js' // 发布 PubSub.publish('search', { isFirst: false, isLoading: true }) // 订阅 // 订阅的组件在挂载时进行订阅 componentDidMount() { this.searchToken = PubSub.subscribe('search', (_, data) => { this.setState(data) }) } // 组件卸载时取消订阅 componentWillUnmount() { PubSub.unsubscribe(this.searchToken) } ``` - `Fetch`:原生函数,不再使用XmlHttpRequest对象提交ajax请求;老版本浏览器兼容性不好 ```js fetch(this.state.url).then( res => { console.log('连接服务器成功') return res.json() // 返回一个promise实例 } ).then( res => { console.log('获取数据成功', res.items.length) } ).catch( err => { console.log(err) } ) // 优化 getDataByFetch = async () => { try { const response = await fetch(this.state.url) const result = await response.json() console.log(result.items.length) } catch (err) { console.log(err) } } ``` - 路由 `router`:底层基于浏览器的历史记录的原理实现:1. `History.createBrowserHistory()`;2. `History.createHasHistory()`,浏览器的 `history` 是个 `栈` 结构,通过 `push()`、`replace()`、`goForward()`(前进)和 `goBack()`(回退)对 `history` 进行操作。 - `react-router-dom`: - Router 直接包裹在 App 组件的外层 ```js import { BrowserRouter } from 'react-router-dom' ReactDOM.render( , document.getElementById('main') ) ``` - 路由组件放在 pages 目录下 ```js import { Link, Route } from 'react-router-dom'
  • Home
  • Shop
  • About
``` - NavLink:为选中的路由添加 `active` 类名,也可以自定义 ```html Home ``` - 以标签体传递的属性,可以在组件的 `this.props.children` 得到,NavLink 标签会将 children 属性作为标签体中的内容 ```html {this.props.children} ``` - Switch 包裹注册的路由,在进行路由匹配的时候就只展示匹配到的第一个路由组件 ```html ``` - 外部引入的CSS文件在路由更改后,样式丢失的问题 1. 将引用路径改为绝对路径 ```html ``` 2. 直接写(默认是在 `public` 目录下) ```html ``` 3. *`HashRouter`* -- 不常用 ```js ``` - 路由默认是模糊匹配 即:路由链接中的路径只要包含路由绑定的路径,且必须是第一个就包含 ```html home ``` 开启精准(严格)匹配,不能随意开启,可能会导致二级路由失效 ```html home ``` - `Redirect` 路由重定向,通常写在注册路由的下方,当所有路由都无法匹配时,执行Redirect ```html ``` - 路由组件传参 1. 向路由组件传递params参数 -- *在地址栏暴露传递的信息* ```html {v.title} ``` ```js // 组件接收参数 const { id, title } = this.props.match.params return (
  • Id: {id}
  • Title: {title}
) ``` 2. 向路由组件传递search参数 -- *在地址栏暴露传递的信息* ```html {v.title} ``` ```js // 接收search参数 const search = this.props.location.search.slice(1) const { id, title } = qs.parse(search) ``` 3. 向路由组件传递state参数 -- *在地址栏不暴露信息* ```html {v.title} ``` ```js // 接收state参数 // 防止浏览器清理缓存,需要给数据添加备用数据 const { id, title } = this.props.location.state || {} ``` - 路由跳转默认是进行 `push` 操作,若想执行 `replace` 操作,则可以直接在 `Link` 标签里添加 `replace` 属性 ```html {v.title} ``` - 编程式路由导航 ```js // push跳转 pushShow = (id, title) => { // params this.props.history.push(`/home/message/detail/${id}/${title}`) // search this.props.history.push(`/home/message/detail?id=${id}&title=${title}`) // state this.props.history.push('/home/message/detail', { id, title }) } // replace跳转 与push同理 replaceShow = (id, title) => { this.props.history.replace(`/home/message/detail/${id}/${title}`) } this.props.history.go() this.props.history.goForward() this.props.history.goBack() this.props.history.push() this.props.history.replace() ``` - `withRouter`:将一般组件加工为路由组件 ```js import { withRouter } from 'react-router-dom' class Header extends Component { render() { return (
) } } export default withRouter(Header) ``` - `redux`: - 配置 `index.js` ```diff import React from 'react' import ReactDOM from 'react-dom' import { BrowserRouter } from 'react-router-dom' import App from './App' + import { Provider } from 'react-redux' + import store from './redux/store' ReactDOM.render( + + + , document.getElementById('main') ) ``` - 在 `src` 目录下新建一个文件夹 `redux` - `store.js`:基本结构 ```js import { createStore, applyMiddleware, combineReducers } from 'redux' // 引入redux-thunk,用于支持异步action import thunk from 'redux-thunk' // 引入redux-devtools-extension,在浏览器端可以使用redux开发者工具 import { composeWithDevTools } from 'redux-devtools-extension' // 引入各个组件的reducers import count from './count/reducers' import person from './person/reducers' // 汇总所有reducers // combineReducers里的内容就是最后reducers的格式 // 各个组件若想取用redux中的状态就必须按照汇总的属性名,进行取用 const allReducers = combineReducers({ count, person }) export default createStore(allReducers, composeWithDevTools(applyMiddleware(thunk))) ``` - `constant.js`:用以暴露组件进行type类型检验时的type常量,方便后期维护 - `组件名`的目录:用于存放该组件的 `reducers.js` 和 `action.js`。 - `reducers.js`:暴露一个方法,根据当前的状态类型对状态进行操作 ```js const init = [{ name: 'INIT', age: 18 }] export default function personReducer(prevState = init, action) { const { type, data } = action switch (type) { case ADD_PERSON: return [...prevState, data] default: return prevState } } ``` - `action.js`:命名各种状态操作的方法名 ```js // 创建增加人动作对象 export const addPerson = (person) => ({ type: ADD_PERSON, data: person }) ``` - 使用 `redux` 的组件整体结构 ```js // 必备模块 import React, { Component } from 'react' import { connect } from 'react-redux' // 引入该组件对应的action.js文件中的状态操作方法 import { addPerson } from '../../redux/person/action' // 以类形式定义的组件 class Person extends Component { // 各种属性和方法都挂载在this.props上 addPerson = () => { const name = this.nameNode.value const age = +this.ageNode.value this.props.addPerson({ name, age }) } render() { return (

Count组件求和为:{this.props.sum}

this.nameNode = c} type="text" placeholder="输入名字" /> this.ageNode = c} type="text" placeholder="输入年龄" />

PersonList:

    { this.props.personList.map((v, i) => { return
  • {v.name}---{v.age}
  • }) }
) } } // 从redux中获取状态 const mapStateToProps = state => ({ personList: state.person, sum: state.count }) // 按redux的形式暴露该组件 export default connect( mapStateToProps, // state { addPerson }, // 从action文件中引入的方法名 )(Person) ``` - 扩展内容 - `setState` 两种写法 ```js // 对象式setState // 适用于:新状态不依赖原状态 const { m } = this.state this.setState( { m: m + 1 }, () => { console.log('state改变后的回调函数') } ) // 函数式setState // 适用于:新状态依赖原状态 this.setState((state, props) => ({ n: state.m + 1 })) // => // this.setState( state => ({ n: state.m + 1 })) ``` - `lazyLoad` 懒加载 ```jsx // 引入lazy和Suspense import React, { Component, lazy, Suspense } from 'react' // 引入需要懒加载的组件 const Home = lazy(() => import('./pages/Home/Home')) const Shop = lazy(() => import('./pages/Shop/Shop')) const About = lazy(() => import('./pages/About/About')) // 懒加载包裹 loading...}> // 注册路由 // 路由重定向 ``` - `Hooks`:在函数组件中使用 `state` 以及其它的 `React` 特性 - `State Hook`:React.useState() ```js const [data, setData] = React.useState(0) // data: 将0作为初始值赋给data // setData: 为更改状态值的函数 import React from 'react' export default function Header() { const [count, setCount] = React.useState(0) const [name, setName] = React.useState('Tom') function add() { // setCount(count + 1) setCount(c => c + 1) setName(c => c + count) } return (

Count: {count}

Name: {name}

) } ``` - `Effect Hook`:React.useEffect(),在函数组件中使用模拟类组件的生命周期钩子 ```js React.useEffect(() => { console.log('相当于componentDidMount()') return () => { console.log('相当于componentWillUnmount()') // ReactDOM.unmountComponentAtNode() } }, []) React.useEffect(() => { console.log('相当于render()') }) const [count, setCount] = React.useState(0) React.useEffect(() => { console.log('相当于componentDidUpdate()监听count值的更新') }, [count]) ``` - `Ref Hook`:在函数组件中使用Ref ```js const input = React.useRef() ``` - `Fragment`:去除组件外层包裹的 `div` ```jsx import { Fragment } from 'react' return ( ) // Fragment可以在标签里定义key值,用于遍历 // => return ( <> ) // 空标签里不能定义任何属性,否则会警告丢失 ``` - `Context`:一种组件间通信方式,常用于【祖先组件】和【后代组件】间通信 1. 只适用于类式组件使用 ```js // 在祖先组件中定义一个可供后代组件得到的context const MyContext = React.createContext() // 在引入后代组件的地方用标签包裹 // 传递的值必须以value作为属性名 ``` ```js // 子组件声明接收context static contextType = MyContext // 传递的值都在this.context中 this.context ``` 2. 两种组件通用接收context方法 ```js { value => { console.log(value) } } ``` - 跨文件间传递context,需要新建一个 `JS` 文件用于暴露一个 `React.createContext()`,然后分别在祖先组件和后代组件中引入即可 - `PureComponent`:组件优化 ```js import { PureComponent } from 'react' class Parent extends PureComponent { render(){ return (
) } } class Child extends PureComponent { render(){ return (
) } } ``` - 继承 `PureComponent` 的组件,内部不能使用 `shouldComponentUpdate` 方法 - `render props`:在未知两个组件父子关系的情况下,传递属性 ```js class App extends Component { render() { return (
} /> ) } } class A extends Component { render() { return (

this is A

{this.props.render({name: 'ZhangSan'})} ) } } class B extends Component { render() { return (

this is B, {this.props.name}

) } } ``` - 错误边界:只能捕获子组件 **`生命周期`** 中产生的错误,开发环境下,提示错误后会再次报错;生产环境下,会照常提示错误 ```js // 在父组件中定义一个错误状态变量 `hasError` state = { hasError: '' } // 错误捕获钩子 static getDerivedStateFromError(error) { console.log(error) return { hasError: error } } // 统计出错,返回给服务器 componentDidCatch() { console.log('APP子组件出错') } // 在渲染子组件的地方添加判断 {this.state.hasError ?

当前网络错误,请稍后再试

:
} ```