react
在企业项目中已经变成了一个必不可少的UI
框架,从过去早期有jquery
,后面有bootstrap
兴起,jquery
可以说二次封装的原生js
,bootstarp
可以快速搭建一个精美网页,现在基本很少用bootstrap
和jquery
了,基本上vuejs
,
react
,angular
三分天下,国内vuejs
和react
居多,angular
很少用,本文是一篇笔者关于react
相关的笔记,希望看完在项目中有所思考和帮助。
在开始本文之前,主要会从以下几个方面去认识学习react
1、没有概念,用实际例子感受react
核心思想
2、react
数据流是怎么样,父子通信,react
是如何更新数据
3、class
组件与纯函数
组件
4、react
的状态提升
5、react
组合概念
6、react
设计哲学
正文开始...
class组件
react
的理念就是构建UI的一个库,很大一个特征就是申明式
,组件化
,跨平台
- 申明式
import React from "react"; import ReactDOM from 'react-dom/client'; class HelloMessage extends React.Component { constructor(props) { super(props); this.state = { count: 0 } } render() { return ( <div> Hello {this.props.name} </div> ); } } const root = ReactDOM.createRoot(document.getElementById('hello-example')) root.render(<HelloMessage name="Web技术学苑" />);
从以上我们知道就是用class
申明了一个HelloMessage
的组件,继承了React.Component
组件,然后通过root.render()
挂载到dom
上,我们也会发现HelloMessage
这个class
组件中的render
中返回的就是一个jsx
,这就是挂载在dom
上的具体内容
纯函数组件
以上是class
方式写的一个组件,但是从react16.8
版本后就出现了hook
,也就是过去纯函数组件没有自己的状态,但是有了hook
后,纯函数组件就可以有自己的状态了。我们完全可以用函数组件
和hook
替代class
组件,上面一段代码,如果用函数组件
就是下面这样的
import React, { useState } from "react"; import ReactDOM from 'react-dom/client'; const HelloMessage = (props) => { const [count, setCount] = useState(0); return ( <div> Hello {props.name} </div> ); } const root = ReactDOM.createRoot(document.getElementById('hello-example')) root.render(<HelloMessage name="Web技术学苑" />);
状态提升
我们知道每个组件有自己的state
,如果同一个组件内部都是自己的state
,那么组件之间就是互相独立,但是此时我想让一个组件输入值,也会影响另一个组件,那么此时就需要两个组件的数据依赖来源就必须提升到父组件里去,所以这就是状态提升。
我们以人民币换算美元汇率的例子加深对状态提升
概念的理解
import React from "react"; class List extends React.Component { constructor(props) { super(props); this.state = { count: 0, price: 0, type: 0, // 0 人民币 1 代表美元 } } handleAdd = () => { // this.setState({ // count: ++this.state.count // }) this.setState(state => { return { ...state, count: state.count + 1 } }) } handleReduce = () => { this.setState(state => { return { count: state.count - 1 } }) } handleRmbInput = (e) => { const val = e.target.value; console.log(e) this.setState({ price: val, type: 0 }) } handleDollInput = (e) => { const val = e.target.value; this.setState({ price: val, type: 1 }) } render() { const { count, price, type } = this.state; const RmbInput = ({ price, handleChange }) => { return (<fieldset> <legend>人民币</legend> <input value={price} onChange={handleChange} /> </fieldset>) }; const DollarInput = ({ price, handleChange }) => { return (<fieldset> <legend>美元</legend> <input value={price} onChange={handleChange} /> </fieldset>) }; const rmbVal = type === 0 ? price : price * 7.34; const dollVal = type === 1 ? price : 0.14 * price; return (<div className="list-app" style={{ padding: '10px' }}> <hr /> <button onClick={this.handleAdd}>+</button> <span>{count}</span> <button onClick={this.handleReduce}>-</button> <hr /> <RmbInput handleChange={this.handleRmbInput} price={rmbVal} /> <hr></hr> <DollarInput handleChange={this.handleDollInput} price={dollVal} /> </div>) } } export default List
我们看到List
这个组件返回的有RmbInput
与DollarInput
组件,你会发现实际上这两个组件的共同特征就是都传入了两个props
到子组件中,注意其中一个是传入的是方法handleChange
,通常在react
中数据流是单向的,所以修改传入的子组件的props
,通常是通过父组件的传入子组件的回调方法去修改传入子组件的props
比如说下面这样一段伪代码
// Parant function Parent() { const { name, setName} = useState("Web技术学苑"); return (<div> <h1>{name}</h1> <Child name={name} onChangeName={val => setName(val)}/> </div>) } // Child function Child(props) { return (<div> <input value={props.name} onChange={e => props.onChangeName(e.target.value)}/> </div>) }
最后我们看下最终的效果
因此一个计算汇率的功能就OK了
但是我们发现实际上RmbInput
与DollarInput
做的事情都是非常类似的,只有名称不一样,因此我们可以将这两个组件合并成一个组件,只需传入一个参数来判断名称即可。
import React from "react"; class List extends React.Component { constructor(props) { super(props); this.state = { count: 0, price: 0, type: 0, // 0 人民币 1 代表美元 } } ... render() { const { count, price, type } = this.state; // 合并成一个组件了 const ExchangeInput = ({ price, handleChange, type }) => { return (<fieldset> <legend>{type === "RmbInput" ? '人民币' : '美元'}</legend> <input value={price} onChange={handleChange} /> </fieldset>) } const rmbVal = type === 0 ? price : price * 7.34; const dollVal = type === 1 ? price : 0.14 * price; return (<div className="list-app" style={{ padding: '10px' }}> <hr /> <button onClick={this.handleAdd}>+</button> <span>{count}</span> <button onClick={this.handleReduce}>-</button> <hr /> <ExchangeInput handleChange={this.handleRmbInput} price={rmbVal} type="RmbInput"></ExchangeInput> <hr/> <ExchangeInput handleChange={this.handleDollInput} price={dollVal} type="DollarInput"></ExchangeInput> </div>) } } export default List
因此从状态提升
来看,react
组件的state
本该相互独立,但是如果想实现一个组件修改关联另一个组件修改,那么数据来源必须依赖父组件,所以也就只能把当前组件的state
提升到父组件里去,从而实现了状态提升。
这里我们也发现,react
提供给子组件的通信就是props
,修改当前组件的state
就是依赖setState
,要想修改父组件数据就是通过props
传入子组件的回调去修改的。
react组合
在react组合
类似vue
的插槽一样的概念,不过有些区别
props.children
就是默认渲染所有父组件的插槽
内容
import React from "react"; const Title = (props) => { return (<div className="title" style={{ display: 'flex' }}> {props.children} </div>) } const About = () => <div> <Title> <h1>Web技术学苑</h1> <div>Maic</div> </Title> </div> export default About;
这样你看到的结果就是下面这样的
你也可以通过props
做类似的vue
具名插槽的功能
import React from "react"; const Title = (props) => { return (<div className="title" style={{ display: 'flex' }}> {props.children} </div>) } const Content = (props) => { return (<div className="content" style={{ ...props.style }}> {props.left} {props.children} {props.right} </div>) } const About = () => { const Left = () => (<div>left</div>); const Right = () => (<div>right</div>); return (<div> <Title> <h1>Web技术学苑</h1> <div>Maic</div> </Title> <Content style={{ padding: '10px' }} left={<Left />} right={<Right />} > <div>center</div> </Content> </div>) } export default About;
我们发现Content
这个组件的props
可以是对象,可以是jsx
,也可以是函数,当子组件用父组件的props
时,我们就当它变量一样在子组件中使用,因此react
中的props
是相当灵活的。
react哲学
react哲学
[1]官方已经用了一篇文章来阐述,通篇下来,官网已经用了一个实际例子来解释react
的哲学思想,总结下来,其实就是以下几点。
1、如何将复杂的UI模块拆分成更细粒度的组件,我们将一个页面拆分成组件,组件依赖数据更清晰,组件之间的耦合度更低。
2、组件的接口props
与state
,要明确知道当前组件的state
是应该放在顶层父组件中,还是当前自身组件
3、因为react
数据流是单向的,在实现父子组件数据流双向过程中,通常用回调
来传递子组件向父组件传递的数据
官方把react哲学
分成了五步走概念,关于组件的拆分,其实我们心里在写代码之前就应该明白哪些组件适合独立拆分,哪些组件不用拆分,当一个页面过于复杂时,此时我们考虑拆分组件的同时,也需要考虑是否组件过度拆分带来的负担,这个因具体情况而定
总结
- 理解
react
构建UI的两种方式,一种是class
方式,一种纯函数
组件方式 react
数据通信,父子组件如何通信- 当多个组件存在互相影响时,此时得考虑
状态提升
,每个独立的组件状态数据依赖来源必须从顶层组件中传入,并且当需要更新props
时,考虑回调函数修改 - 在
react
中实现vue
插槽的功能,也就是react
的组合,props.children
会默认渲染父组件插槽
功能,通过props
指定jsx
可以可以实现具名插槽功能 - 理解
react哲学
思想,让我们更灵活的运用react
构建一个完整,维护性很强的web
应用 - code example[2]