React 是一个用于构建用户界面的 JavaScript 库,它只负责应用的视图层,帮助开发人员构建快速且交互式的 web 应用程序。

1 基本使用

1.1 创建虚拟 DOM:

  • React.createElement(component, props, ...children)
  • JSX (第一种方式的语法糖) ```jsx // 1.创建虚拟DOM const VDOM1 = React.createElement( ‘h1’, { id: ‘title’ }, React.createElement(‘span’, {}, ‘Hello,React1’) );

const VDOM2 = (

Hello,React2

);

// 2.渲染虚拟DOM ReactDOM.render(VDOM2, document.getElementById(‘root’));

  1. 虚拟DOM打印:<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/338495/1650195647987-7d28ee2f-e242-430f-9db1-7a4dc25407af.png#clientId=u3f2fd0b6-cc86-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=203&id=u6f2696f6&margin=%5Bobject%20Object%5D&name=image.png&originHeight=254&originWidth=477&originalType=binary&ratio=1&rotation=0&showTitle=false&size=31169&status=done&style=none&taskId=u47dd7240-e0fc-4923-a627-3a96b7b918a&title=&width=381.6)
  2. <a name="nZCgs"></a>
  3. ## 1.2 JSX
  4. 1. 定义虚拟 DOM 时,不要写引号。
  5. 2. 标签中混入** JS 表达式**时要用`{}`
  6. 3. 样式的类名指定不要用 class,要用 `className`
  7. 4. 内联样式,要用 `style={{key:value}}` 的形式去写。
  8. 5. 只有一个根标签
  9. 6. 单标签必须闭合
  10. 7. 会自动展开数组
  11. 8. 标签首字母
  12. 1. 若小写字母开头,则将该标签转为 html 中同名元素,若 html 中无该标签对应的同名元素,则报错
  13. 2. 若大写字母开头,react 就去渲染对应的组件,若组件没有定义,则报错。
  14. ```jsx
  15. const myId = '0001';
  16. const myData = 'React';
  17. const data = ['Angular', 'React', 'Vue'];
  18. const VDOM3 = (
  19. <div>
  20. <h2 className="title" id={myId.toLowerCase()}>
  21. <span style={{ color: 'red', fontSize: '29px' }}>
  22. {myData.toUpperCase()}
  23. </span>
  24. </h2>
  25. <ul>
  26. {data.map((item, index) => {
  27. return (
  28. <li key={index}>
  29. {index}:{item}
  30. </li>
  31. );
  32. })}
  33. </ul>
  34. <input type="text" />
  35. </div>
  36. );

if 语句以及 for 循环不是 JavaScript 表达式,所以不能在 JSX 中直接使用 表达式:

  • a
  • a+b
  • fn()
  • arr.map()
  • function test(){}

语句:

  • if(){}
  • for(){}
  • switch(){}

1.3 组件化

  • 组件名必须首字母大写
  • 只能有一个根元素,必须有结束标签

  • 函数式组件

    1. function MyComponent1() {
    2. //此处的this是undefined,因为babel编译后开启了严格模式
    3. console.log(this);
    4. return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>;
    5. }
  • 类式组件

    1. class MyComponent2 extends React.Component {
    2. render() {
    3. //render是放在哪里的?—— MyComponent的原型对象上,供实例使用。
    4. //render中的this是谁?—— MyComponent的实例对象 <=> MyComponent组件实例对象。
    5. console.log('render中的this:', this);
    6. return <h2>我是用类定义的组件(适用于【复杂组件】的定义)</h2>;
    7. }
    8. }

    渲染类组件标签的基本流程

  • 创建组件实例对象,调用 render 得到虚拟DOM,解析为真实DOM,插入到页面

  • 渲染组件
    1. ReactDOM.render(<MyComponent/>, document.getElementById('root'));

1.4 组件实例三大属性

state

state 是组件对象最重要的属性, 值是对象(可以包含多个 key-value 的组合),不能直接修改或更新

  • 组件中 render 方法中的 this 为组件实例对象

组件自定义的方法中 this 为 undefined,如何解决?

  • 强制绑定 this: 通过函数对象的 bind()
  • 箭头函数

    1. class Toggle extends React.Component {
    2. constructor(props) {
    3. super(props);
    4. this.state = {isToggleOn: true};
    5. // 为了在回调中使用 `this`,
    6. this.handleClick = this.handleClick.bind(this);
    7. }
    8. handleClick() {
    9. this.setState(prevState => ({
    10. isToggleOn: !prevState.isToggleOn
    11. }));
    12. }
    13. render() {
    14. return (
    15. <button onClick={this.handleClick}>
    16. {this.state.isToggleOn ? 'ON' : 'OFF'}
    17. </button>
    18. );
    19. }
    20. }

    使用箭头函数 ```jsx export default class Toggle extends React.Component { state = { isHot: false, wind: ‘微风’ };

    // 箭头函数 changeWeather = () => { const isHot = this.state.isHot; this.setState({ isHot: !isHot }); }; render() { const { isHot, wind } = this.state; return (

    1. <h1 onClick={this.changeWeather}>
    2. 今天天气很{isHot ? '炎热' : '凉爽'},{wind}
    3. </h1>

    ); } }

  1. this指向问题:
  2. ```jsx
  3. class Cat {
  4. sayThis () {
  5. console.log(this); // 这里的 `this` 指向谁?
  6. }
  7. exec (cb) {
  8. cb(); // 回调函数 没有显示调用者指向 undefined
  9. }
  10. render () {
  11. this.exec(this.sayThis);
  12. }
  13. }
  14. const tom = new Cat();
  15. tom.render(); // undefined
  16. // ES6 的 class 语法,自动地使用严格模式,非严格模式下指向全局对象
  17. // 当你使用 onClick={this.handleClick}来绑定事件监听函数的时候,
  18. // handleClick 函数实际上会作为回调函数,传入 addEventListener()
  1. class Demo {
  2. constructor() {
  3. this.name = 'demo';
  4. }
  5. handleClick1 = () => {
  6. console.log(this.name);
  7. };
  8. handleClick2() {
  9. console.log(this.name);
  10. }
  11. }
  12. const demo = new Demo();
  13. const fn1 = demo.handleClick1;
  14. const fn2 = demo.handleClick2;
  15. console.log(demo.handleClick1()); // demo
  16. console.log(demo.handleClick2()); // demo
  17. console.log(fn1()); // demo
  18. console.log(fn2()); // this undefined

class 中的方法如果是普通函数方法,该方法会绑定在构造函数的原型上 但是如果方式是箭头函数方法,该方法会绑定实例上

props

  • 通过标签属性从组件外向组件内传递变化的数据
  • 注意: 组件内部不要修改 props 数据,单向数据流 ```jsx import React from ‘react’;

export default class Person extends React.Component { constructor(props) { //构造器是否接收props,是否传递给super,取决于是否希望在构造器中通过this访问props super(props); console.log(‘constructor’, this.props); }

//对标签属性进行类型、必要性的限制 static propTypes = { name: PropTypes.string.isRequired, //限制name必传,且为字符串 sex: PropTypes.string, //限制sex为字符串 age: PropTypes.number, //限制age为数值 };

//指定默认标签属性值 static defaultProps = { sex: ‘男’, //sex默认值为男 age: 18, //age默认值为18 };

render() { const { name, age, sex } = this.props; //props是只读的 return (

  • 姓名:{name}
  • 性别:{sex}
  • 年龄:{age + 1}
); } }

//渲染组件到页面 ReactDOM.render(,document.getElementById(‘test1’))

  1. props 中的属性值进行类型限制和必要性限制:
  2. - ~~propTypes ~~方式已弃用
  3. ```jsx
  4. Person.propTypes = {
  5. name: React.PropTypes.string.isRequired,
  6. age: React.PropTypes.number
  7. }
  • 使用 prop-types 库进限制(需要引入 prop-types 库)
    1. import PropTypes from 'prop-types';
    2. Person.propTypes = {
    3. name: PropTypes.string.isRequired,
    4. age: PropTypes.number.
    5. }

refs

refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素。

  • React.createRef()

    1. class MyComponent extends React.Component {
    2. constructor(props) {
    3. super(props);
    4. this.myRef = React.createRef();
    5. }
    6. render() {
    7. return <div ref={this.myRef} />;
    8. }
    9. }
  • 回调形式

    1. class CustomTextInput extends React.Component {
    2. constructor(props) {
    3. super(props);
    4. this.textInput = null;
    5. this.setTextInputRef = element => {
    6. this.textInput = element;
    7. };
    8. }
    9. render() {
    10. // 使用 `ref` 的回调函数将 text 输入框 DOM 节点的引用存储到 React
    11. // 实例上(比如 this.textInput)
    12. return (
    13. <div>
    14. <input
    15. type="text"
    16. ref={this.setTextInputRef}
    17. />
    18. </div>
    19. );
    20. }
    21. }

    如果 ref 回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数 null,然后第二次会传入参数 DOM 元素。这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的。


  • String 类型的 Refs

例如ref="textInput"。可以通过this.refs.textInput 来访问 DOM 节点。不建议使用它,因为 string 类型的 refs 存在 一些问题。它已过时并可能会在未来的版本被移除。

事件处理

  1. 通过onXxx 属性指定事件处理函数(注意大小写)
  • React 使用的是自定义(合成)事件, 而不是使用的原生DOM事件 —————— 为了更好的兼容性
  • React 中的事件是通过事件委托方式处理的(委托给组件最外层的元素) ————————为了的高效
  1. 通过 event.target 得到发生事件的DOM元素对象 ——————————不要过度使用ref

1.5 受控组件

在一个受控组件中,表单数据是由 React 组件来管理的。另一种替代方案是使用非受控组件,这时表单数据将交由 DOM 节点来处理。

  • 非受控组件

    1. class Login extends React.Component{
    2. handleSubmit = (event)=>{
    3. event.preventDefault() //阻止表单提交
    4. const {username,password} = this
    5. alert(`你输入的用户名是:${username.value},你输入的密码是:${password.value}`)
    6. }
    7. render(){
    8. return(
    9. <form onSubmit={this.handleSubmit}>
    10. 用户名:<input ref={c => this.username = c} type="text" name="username"/>
    11. 密码:<input ref={c => this.password = c} type="password" name="password"/>
    12. <button>登录</button>
    13. </form>
    14. )
    15. }
    16. }
  • 受控组件

    1. class Login extends React.Component {
    2. //初始化状态
    3. state = {
    4. username: '', //用户名
    5. password: '' //密码
    6. }
    7. //保存用户名到状态中
    8. saveUsername = (event) => {
    9. this.setState({ username: event.target.value })
    10. }
    11. //保存密码到状态中
    12. savePassword = (event) => {
    13. this.setState({ password: event.target.value })
    14. }
    15. //表单提交的回调
    16. handleSubmit = (event) => {
    17. event.preventDefault() //阻止表单提交
    18. const { username, password } = this.state
    19. alert(`你输入的用户名是:${username},你输入的密码是:${password}`)
    20. }
    21. render() {
    22. return (
    23. <form onSubmit={this.handleSubmit}>
    24. 用户名:<input onChange={this.saveUsername} type="text" name="username" />
    25. 密码:<input onChange={this.savePassword} type="password" name="password" />
    26. <button>登录</button>
    27. </form>
    28. )
    29. }
    30. }

2 声明周期

生命周期的三个阶段(旧)

2_react生命周期(旧).png
1.初始化阶段: 由 ReactDOM.render()触发—-初次渲染

  1. constructor()
  2. componentWillMount()
  3. render()
  4. componentDidMount()

2.更新阶段: 由组件内部this.setSate()或父组件重新 render 触发
a. shouldComponentUpdate()
b. ~~componentWillUpdate() ~~ // 组件将要更新的钩子
c. render()
d. componentDidUpdate() // 组件更新完毕的钩子

  1. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
    a. componentWillUnmount()

生命周期的三个阶段(新)

3_react生命周期(新).png1. 初始化阶段: 由 ReactDOM.render()触发—-初次渲染
a. constructor()
b. getDerivedStateFromProps 若state的值在任何时候都取决于props,那么可以使用
c. render()
d. componentDidMount() : 组件挂载完毕的钩子,一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息

  1. 更新阶段: 由组件内部this.setSate()或父组件重新 render 触发
    a. getDerivedStateFromProps
    b. shouldComponentUpdate() 控制组件更新的“阀门”
    c. render()
    d. getSnapshotBeforeUpdate 在更新之前获取快照
    c. componentDidUpdate() 组件更新完毕的钩子

  2. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发

  3. componentWillUnmount(): 组件将要卸载的钩子,一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息

  1. shouldComponentUpdate(){
  2. console.log('Count---shouldComponentUpdate');
  3. return true
  4. }

重要的勾子:

  1. render:初始化渲染或更新渲染调用
  2. componentDidMount:开启监听, 发送 ajax 请求
  3. componentWillUnmount:做一些收尾工作, 如: 清理定时器

即将废弃的勾子:

  1. componentWillMount
  2. componentWillReceiveProps
  3. componentWillUpdate

3 setState

setState(stateChange, [callback])-——-对象式的setState

  • stateChange为状态改变对象(该对象可以体现出状态的更改)
  • callback是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用

    1. <br />`setState(updater, [callback])-`-----函数式的setState
  • updater 为返回stateChange对象的函数。

  • updater 可以接收到state和props。
  • callback 是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。

总结:
1.对象式的setState是函数式的setState的简写方式(语法糖)
2.使用原则:
(1).如果新状态不依赖于原状态 ===> 使用对象方式
(2).如果新状态依赖于原状态 ===> 使用函数方式
(3).如果需要在setState()执行后获取最新的状态数据,要在第二个callback函数中读取

4 Hooks

Hooks 是React 16.8.0版本增加的新特性/新语法,可以让你在函数组件中使用 state 以及其他的 React 特性

三个常用的Hook

  • State Hook:React.useState()
  • Effect Hook: React.useEffect()
  • Ref Hook: React.useRef()

State Hook

  1. State Hook让函数组件也可以有 state 状态, 并进行状态数据的读写操作
  2. 语法:const [xxx, setXxx] = React.useState(initValue)
  3. useState()说明:

    1. 参数: 第一次初始化指定的值在内部作缓存<br /> 返回值: 包含2个元素的数组, 1个为内部当前状态值, 2个为更新状态值的函数
  4. setXxx()2种写法:

    1. setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值
    2. setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值

Effect Hook
Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
React中的副作用操作:

  • 发ajax请求数据获取
  • 设置订阅 / 启动定时器
  • 手动更改真实DOM

    1. useEffect(() => {
    2. // 在此可以执行任何带副作用操作
    3. return () => { // 在组件卸载前执行
    4. // 在此做一些收尾工作, 比如清除定时器/取消订阅等
    5. }
    6. }, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行

    可以把 useEffect Hook 看做如下三个函数的组合:

  • componentDidMount()

  • componentDidUpdate()
  • componentWillUnmount()

Ref Hook

  • Ref Hook 可以在函数组件中存储/查找组件内的标签或任意其它数据
  • 语法: const refContainer = useRef()
  • 作用:保存标签对象,功能与React.createRef()一样

5 组件通信方式总结

几种通信方式:

  1. props:
  • children props
  • render props
  1. 消息订阅-发布:pubs-sub、event等等
  2. 集中式管理:redux、dva等等
  3. context: 生产者-消费者模式

最佳实践:

  • 父子组件:props
  • 兄弟组件:消息订阅-发布、集中式管理
  • 祖孙组件(跨级组件):消息订阅-发布、集中式管理、context(开发用的少,封装插件用的多)

6 路由

下载:npm install react-router-dom

5.1.1 路由基本使用

  1. // App.js
  2. import React from 'react';
  3. import { BrowserRouter as Router, Route, Link } from 'react-router-dom';
  4. function Index() {
  5. return <div>首页</div>;
  6. }
  7. function News() {
  8. return <div>新闻</div>;
  9. }
  10. function App() {
  11. return (
  12. <Router>
  13. <div>
  14. <Link to="/index">首页</Link>
  15. <Link to="/news">新闻</Link>
  16. </div>
  17. <div>
  18. <Route path="/index" component={Index}/>
  19. <Route path="/news" component={News}/>
  20. </div>
  21. </Router>
  22. );
  23. }

5.1.2 路由嵌套

  1. function News(props) {
  2. return (
  3. <div>
  4. <div>
  5. <Link to={`${props.match.url}/company`}>公司新闻</Link>
  6. <Link to={`${props.match.url}/industry`}>行业新闻</Link>
  7. </div>
  8. <div>
  9. <Route path={`${props.match.path}/company`} component={CompanyNews} />
  10. <Route path={`${props.match.path}/industry`} component={IndustryNews}/>
  11. </div>
  12. </div>
  13. );
  14. }
  15. function CompanyNews() {
  16. return <div>公司新闻</div>
  17. }
  18. function IndustryNews() {
  19. return <div>行业新闻</div>
  20. }

5.1.3 路由传参

  1. import url from 'url';
  2. class News extends Component {
  3. constructor(props) {
  4. super(props);
  5. this.state = {
  6. list: [{
  7. id: 1,
  8. title: '新闻1'
  9. }, {
  10. id: 2,
  11. title: '新闻2'
  12. }]
  13. }
  14. }
  15. render() {
  16. return (
  17. <div>
  18. <div>新闻列表组件</div>
  19. <ul>
  20. this.state.list.map((item, index) => {
  21. return (
  22. <li key={index}>
  23. <Link to={`/detail?id=${item.id}`}>{item.title}</Link>
  24. </li>
  25. );
  26. })
  27. </ul>
  28. </div>
  29. );
  30. }
  31. }
  32. class Detail extends Component {
  33. constructor(props) {
  34. super(props);
  35. }
  36. const { query } = url.parse(this.props.location.search, true);
  37. console.log(query); // {id: 1}
  38. render() {
  39. return <div>新闻详情</div>
  40. }
  41. }

5.1.4 路由重定向

  1. import { Redirect } from 'react-router-dom';
  2. class Login extends Component {
  3. render() {
  4. if (this.state.isLogin) {
  5. return <Redirect to="/"/>
  6. }
  7. }
  8. }