初始化React
从 html 开始你的第一个 react 页面。引入三个文件:react核心文件、react-dom文件和转化jsx的babel。
然后声明一个变量,变量的值是一个不带引号的标签,最后使用 ReactDOM.render 方法把该变量渲染到页面上。
<div id="app"></div>
<script type="text/babel">
const VDOM = <h1>你好,里爱特</h1>
ReactDOM.render(VDOM,document.querySelector('#app'))
</script>
创建虚拟DOM的两种方式
第一种:JSX语法
现阶段需借助babel
<div id="app"></div>
<script type="text/babel">
const VDOM = (
<h1>
<p class="hello-react">你好,里爱特</p>
</h1>
)
ReactDOM.render(VDOM,document.querySelector('#app'))
</script>
第二种:借助 React.createElement
此方法仅供了解,后续也不会使用
<div id="app"></div>
<script>
const VDOM = React.createElement('h1',{},React.createElement('span',{class:"hello-react"},'你好,里爱特'))
ReactDOM.render(VDOM,document.querySelector('#app'))
</script>
参数一,元素类型。参数二,配置对象,用于写元素属性。参数三,元素的内容
关于 虚拟DOM
<div id="app"></div>
<script type="text/babel">
const VDOM = <h1>你好,里爱特</h1>
console.log(VDOM)
console.dir(document.querySeletor('#app'))
</script>
创建出来后打印台输出可以看到 VDOM 本质上就是一个 object 类型的变量,身上带有自身特有的虚拟DOM属性。
与真实 DOM 比较可以发现 react 虚拟 DOM 很轻量
JSX 语法规则
- 虚拟DOM只能有一个根标签。
- 虚拟DOM中每一个标签都必须闭合,单标签例如 input 也要写成自闭合形式。
- JSX 标签变量不能带引号。
- 虚拟DOM中如果想使用JS表达式,需要包裹在花括号里面。
- 虚拟DOM中书写样式的两个注意点
- 如果虚拟 DOM 中想要带上 class ,注意要写成 className,原因是跟ES6的class关键字冲突了。
- 如果虚拟 DOM 中想要写内联样式,style后面的值不能接字符串,需要接一个 JS 表达式,表达式里面是对象形式。
- 虚拟DOM中的标签如果是小写字母开头的标签,JSX 默认解析为 html 原生标签。
虚拟DOM中的标签如果是大写字母开头的标签,JSX 默认解析为 react 组件。 - 虚拟DOM注释
{/* */}
<div id="app"></div>
<script type="text/babel">
const myData = '你好,里爱特!'
const jsxml = (
<div>
<h1 className="red">{myData}</h1>
<input type="text" style={
{color:'blue'}
}/>
<Good>顾得</Good>
</div>
)
ReactDOM.render(jsxml,document.querySelector('#app'))
</script>
使用 react 渲染可迭代数据为DOM
使用JS表达式的形式渲染可迭代数据,例如用 map 渲染数组,map 的回调函数 return 虚拟DOM,且必须携带 key 方便react执行diff算法。
const data = ['angular','react','vue']
const VDOM = (
<div>
<h1>使用react渲染可迭代数据</h1>
<ul>
{
data.map((item,index)=>{
return <li key={index}>{item}</li>
})
}
</ul>
</div>
)
ReactDOM.render(VDOM,document.querySelector('#app'))
组件
在使用 ReactDOM.render() 方法的时候,第一个参数传入一个标签,就会寻找与标签同名的 function 或 class,进而去创建组件实例。
函数式组件(简单组件)
function MyComponent(){
return <h2>我是react函数式组件,我的this指向为严格模式下的js的undefined</h2>
}
ReactDOM.render(<MyComponent/>,document.querySelector('#app'))
类式组件(复杂组件)
类式组件式继承 React.Component 父类的之类,必须带有 render() 方法,render中的 this 指向 组件实例对象。
组件实例对象会在调用 ReactDOM.render 时候自动创建出来。
<div id="app"></div>
<script type="text/babel">
class MyComponent extends React.Component {
render() {
console.log(this);
return (
<h1>我是类式组件创建出来的组件</h1>
)
}
}
ReactDOM.render(<MyComponent />, document.querySelector('#app'))
</script>
组件的三大属性
state、props、refs
state
往组件身上添加状态,需要在组件类里的 constructor 里写上 this.state={}
注意在添加state之前必须调用 super()
class RC extends React.Component {
render() {
return (<h1>今天天气很{this.state.hot}</h1>)
}
constructor() {
super()
this.state = {
hot: '热'
}
}
}
ReactDOM.render(<RC />, document.getElementById('app'))
解决虚拟DOM实例上的this指向问题
在 constructor 中利用 bind(this) 解决
class RC extends React.Component {
render() {
const { num } = this.state
return (
<h1 onClick={this.countPlus}>天下第{num}武道会</h1>
)
}
constructor() {
super();
this.state = {
num: 1
};
this.countPlus = this.countPlus.bind(this)
}
countPlus() {
this.state.num++
console.log(this.state.num);
}
}
ReactDOM.render(<RC />, document.getElementById('app'))
setState
上面的例子可以看到页面并没有更新,那是因为直接修改state的话不是响应式的,需要调用 setState 修改状态,才能刷新 render
class RC extends React.Component {
render() {
const { num } = this.state
return (
<h1 onClick={this.countPlus}>天下第{num}武道会</h1>
)
}
constructor() {
super();
this.state = {
num: 1
};
this.countPlus = this.countPlus.bind(this)
}
countPlus() {
let newNum = this.state.num + 1;
this.setState({
num: newNum
})
}
}
ReactDOM.render(<RC />, document.getElementById('app'))
精简组件写法
我们可以看到上面的constructor写法很麻烦,大多数时候并不需要用到类本身,而是用类的实例对象。
在class中直接写赋值语句可以为实例对象直接增加属性,用于事件触发的回调函数也可以写成箭头函数赋值给变量的形式(因为要让函数里的this指向组件实例)
class RC extends React.Component {
render() {
const { name, age } = this.state
return (
<div>
<h1>我叫{name},我今年{age}岁</h1>
<button onClick={this.handleClick}>点我增加岁数</button>
</div>
)
}
state = {
name: 'ming',
age: 18
}
handleClick = () => {
let newAge = this.state.age + 1
this.setState({
age: newAge
})
}
}
ReactDOM.render(<RC />, document.querySelector('#app'))
props
通过渲染组件时往组件身上写属性和属性值可以往组件内部传递数据,通过 this.props.xxx 可以读取传递过来的属性值
<div id="app"></div>
<div id="app2"></div>
<script type="text/babel">
class Person extends React.Component {
render() {
return (
<div>
<ul>
<li>我是:{this.props.name}</li>
<li>年龄:{this.props.age}</li>
<li>性别:{this.props.sex}</li>
</ul>
</div>
)
}
}
ReactDOM.render(<Person name="he" age="18" sex="male" />, document.getElementById('app'))
ReactDOM.render(<Person name="ming" age="20" sex="female" />, document.getElementById('app2'))
props 批量传递
可以传递一个对象,然后用花括号里面展开对象的形式进行 props 传值
class Person extends React.Component {
render() {
return (
<div>
<ul>
<li>我是:{this.props.name}</li>
<li>年龄:{this.props.age}</li>
<li>性别:{this.props.sex}</li>
</ul>
</div>
)
}
}
const qiming = {
name: 'he',
age: 18,
sex: 'male'
}
ReactDOM.render(<Person {...qiming} />, document.getElementById('app'))
在这个例子中,babel+react里仅限组件传值可以这样展开对象,其他情况展开是个空白的东西
props 限制
对props传递的值进行类型限制或必要性限制,需要借助另一个 react 库 prop-type。
通过往组件身上添加 propTypes 属性进行类型限制和必要性限制,添加 defaultProps 进行默认属性值设置。
class Person extends React.Component {
render() {
const { name, age, sex } = this.props
return (
<div>
我是{name}
</div>
)
}
}
Person.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
speak: PropTypes.func.isRequired
}
Person.defaultProps = {
name: 'he'
}
const p1 = {
name: 'qiming',
age: 18,
sex: 'male'
}
const speak = () => {
console.log('haha');
}
ReactDOM.render(<Person {...p1} speak={speak} />, document.querySelector('#app'))
如果要在限制对象内使用进一步限制,使用 PropTypes.shape 方法
PropTypes.shape({
color: PropTypes.string,
fontSize: PropTypes.number
})
props 限制的简写形式
上面的代码,可以使用 static 关键字优化一下
class Person extends React.Component {
render() {
const { name, age, sex } = this.props
return (
<div>
我是{name}
</div>
)
}
static propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
speak: PropTypes.func.isRequired
}
static defaultProps = {
name: 'he'
}
}
函数式组件中的 props
函数式组件中,在形参中的第一个参数,就是props,当然也可以使用 props限制
function RC(props) {
console.log(props);
return <div></div>
}
RC.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
speak: PropTypes.func.isRequired
}
ReactDOM.render(<RC name="ming" />, document.getElementById('app'))
props 注意点
- props 是只读的,单向数据流保持数据的可追溯性
construtor
官方文档:通常,在 React 中,构造函数仅用于以下两种情况:
- 通过给 this.state 赋值对象来初始化内部 state。
- 为事件处理函数绑定实例。
也就是说,组件中的 construtor完全可以省略,但一旦写了,然后super里没有传递props,就会出现 constructor 里的 this.props 丢失的情况,但是可以直接访问 props
class RC extends React.Component {
constructor(props) {
super()
console.log(this.props); //这里输出 undefined
console.log(props); //这里输出props
}
render() {
return <div></div>
}
}
ReactDOM.render(<RC name="ming" />, document.getElementById('app'))
ref 与 refs
ref 是声明在DOM标签上的,refs是用于读取 ref 列表
class RC extends React.Component {
render() {
return (
<div>
<input type="text" ref="input1" />
<button onClick={this.btnClick}>点我看东西</button>
<input onBlur={this.handlerBlur} type="text" ref="input2" />
</div>
)
}
btnClick = () => {
this.refs.input2.value = this.refs.input1.value
}
handlerBlur = () => {
alert(this.refs.input2.value)
}
}
ReactDOM.render(<RC name="ming" />, document.getElementById('app'))
ref的三种形式
- ref 的值为字符串形式:官方不推荐使用,存在一些性能上效率上的问题。
- ref 的值为一个回调函数形式,取的时候直接从this身上取而不是 refs 了
render() {
return (
<div>
<input onBlur={ handleBlur } type="text" ref={ currentNode => this.input1=currentNode } />
</div>
)
}
handleBlur = ()=>{
console.log(111)
}
像这种直接在内联里写回调函数的形式,在更新过程中会被执行两次。通过在类里面直接定义函数然后在节点上使用可以解决这种问题。不过大多时候无关紧要
render() {
return (
<div>
<input onBlur={ handleBlur } type="text" ref={ this.currentNode } />
</div>
)
}
currentNode = (c) => this.input1=c
- 使用 React.createRef() 来创建 ref 标识,读取的时候从 this.容器名.current 才能读取到该容器的 DOM
render() {
return (
<div>
<input onBlur={ handleBlur } type="text" ref={ this.myRef } />
</div>
)
}
myRef = React.createRef()
绑定事件
给虚拟DOM绑定事件,官方推荐用 onXxx 写法,注意 Xxx 开头字母需要大写,例如 onclick 要写成 onClick
class RC extends React.Component {
render() {
return (
<h1 onClick={handleClick}>点我看控制台</h1>
)
}
}
function handleClick() {
console.log('我点击了');
}
ReactDOM.render(<RC />, document.getElementById('app'))
使用 onXxx 写法原因有二:
- React 使用的是自定义事件,而不是使用的原生 DOM 事件
- React 中的事件是通过事件委托方式处理的 (委托给组件最外层的元素)
事件可以通过 event.target 得到发生事件的 DOM 元素对象
表单数据收集
非受控组件
现用现取
受控组件
监听 onChange 事件然后通过 this.setState 设置状态
class RC extends React.Component {
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
用户名:<input onChange={this.handlerUsername} type="text" />
密码:<input onChange={this.handlerPassword} type="password" />
<button>提交</button>
</form>
</div>
)
}
state = {
username: '',
password: ''
}
handlerUsername = (e) => {
this.setState({
username: e.target.value
})
}
handlerPassword = (e) => {
this.setState({
password: e.target.value
})
}
handleSubmit = (e) => {
e.preventDefault()
const { username, password } = this.state
console.log(`用户名是${username},密码是${password}`);
}
}
ReactDOM.render(<RC name="ming" />, document.getElementById('app'))
少用函数优化以上代码
可以使用函数柯里化、内联函数、灵活运用 event 对象等方法优化以上代码
class RC extends React.Component {
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
用户名:<input onChange={this.handleChange} type="text" name="username" />
密码:<input onChange={this.handleChange} type="password" name="password" />
<button>提交</button>
</form>
</div>
)
}
state = {
username: '',
password: ''
}
handleChange = (e) => {
const value = target.type === "checkbox"? target.checked:target.value
this.setState({
[e.target.name]: value
})
}
handleSubmit = (e) => {
e.preventDefault()
const { username, password } = this.state
console.log(`用户名是${username},密码是${password}`);
}
}
ReactDOM.render(<RC name="ming" />, document.getElementById('app'))
双向绑定
注意双向绑定除了要绑定 onChange 事件以外还要把 value 值绑定为state值,否则状态改变时输入框内容不会改变
<input name="username" onChange={this.handleInput} value={this.state.username}></input>
当输入组件的 name 和 state 里面的状态值名一致时就能做到上面的优化
React 生命周期
生命周期(旧)
shouldComponentUpdate钩子默认返回真,如果返回值为假,后面的生命周期都不会走
forceUpdate 强制触发更新
componentWillReceiveProps 第一次接收props不会触发,后面才会触发
[图片上传失败...(image-69038a-1658627198688)]
生命周期>16.4
[图片上传失败...(image-3124ce-1658627198688)]
可以看到新的生命周期多了 static getDerivedStateFromProps 和 getSnapshotBeforeUpdate。
实际上工作中常用的就
static getDerivedStateFromProps
直译 从props衍生的状态,使用这个钩子会影响状态更新,可以接到 props 参数和 state。返回值就成为组件的状态
通常这么用,官方文档说用于 state的值任何时候都取决于 props
static getDerivedStateFromProps(props,state){
return props
}
getSnapshotBeforeUpdate
直译:在更新之前获取快照,必须返回一个 null 或者快照值
该钩子会在最近一次渲染输出(提交到DOM节点)之前调用。该钩子的返回值会传给 componentDidUpdate 的第三个参数
getSnapshotBeforeUpdate(){
return 给componentDidUpdate的值
}
componentDidUpdate
该钩子可以接到两个参数,一个是更新之前的props,另一个是更新前的state,
如果getSnapshotBeforeUpdate return了一个值,就会在第三个参数中接到
componentDidUpdate(preProps,preState,snapshot){
}
如果要在这个钩子中使用 setState,必须放在一个 if 中
样式模块化
在样式文件中间加上 .module. 例如 index.module.css
在引入的时候可以起个名字接住这个样式模块,使用的时候用对象点的形式
import hello from './index.module.css'
export default class hello extends Component{
render(){
return <h2 className={hello.title}></h2>
}
}
组件通信
父子:props
子父:父给子传递函数,子调用函数传递参数
兄弟:第三方库 pubsub-js
import PubSub from 'pubsub-js'
PubSub.subscribe("消息名",回调函数(订阅名,消息内容){})
PubSub.subscribe('userInfo',(_,data)=>{})
PubSub.publish('消息名',消息)
脚手架代理解决跨域
方法一(简单方式):在 package.json 配置
"proxy":"地址"
方法二:在src目录下创建 setupProxy.js
const proxy = require('http-proxy-middleware'
module.exports = function (app){
app.use(
//新版是proxy.createProxyMiddleware
proxy('/api1',{
target: 'http://localhost:5000',
changeOrigin: true,
pathRewrite: {'^/api1': ''}
})
)
}
使用 pathRewrite 的原因是防止与项目目录下的资源同名,
react 路由
注意2022年路由用的是6版本,react16旧一点的用5
npm i react-router-dom@5
路由跳转组件,使用前要从库中引入,Link组件包在Router组件里面。用to属性来指定跳转地址
import {Link,Router} from 'react-router-dom'
<Router>
<Link to="/about"></Link>
</Router>
路由器分为 BrowserRouter 和 HashRouter,直接使用Router会报错
import {Link,BrowserRouter} from 'react-router-dom'
<BrowserRouter>
<Link to="/about"></Link>
</BrowserRouter>
使用 Route 注册路由,两个关键属性 path 指定展示的组件和 component 指定要展示的组件,然后也要用 Router(BrowserRouter 和 HashRouter) 组件包着
import About from './component/About'
import Home from './component/Home'
<BrowserRouter>
<Route path="/home" component={Home} />
<Route path="/about" component={About} />
</BrowserRouter>
而且注意 Link 和 Route 要包在一个 Router 里
<BrowserRouter>
<Link to="/home"></Link>
<Link to="/about"></Link>
<Route path="/home" component={Home} />
<Route path="/about" component={About} />
</BrowserRouter>
尚硅谷的教程里把 </BrowserRouter> 扔在了 index.js 里写在了 App 组件外面
组件分类
组件分为路由组件与一般组件,路由用的组件一般不写在 component 中,写在 pages 或 views 里
路由传参
路由组件默认收到4个props参数:history、location、match、staticcontext
这些参数有几个方法和属性需要关注
history:go,goBack,goForward,push,replace
location:pathname,search,state
match:params,path,url
一般组件插槽
<MyComponent>自定义插槽内容</MyComponent>
上面的自定义插槽内容传到哪里了呢,在组件内部输出this.props,可以看到挂在了 children 上
console.log(this.props.children)
就会输出插槽内容
NavLink
NavLink 组件会默认给活跃的路由组件加上 active 类名,
也可以使用 activeClassName 指定活跃的类名
<NavLInk activeClassName="zidingyi"/>
一个地址匹配多个路由
<Route path="/home" component={Home}>
<Route path="/home" component={About}>
像上面这种情况,router版本5会都显示
Switch组件
Switch组件可以防止上面的情况出现,匹配到了一个路由就不会往下匹配
import {Switch,Route} from 'react-router-dom'
<Switch>
<Route path="/home" component={Home}>
<Route path="/home" component={About}>
</Switch>
多级路由资源丢失问题
低版本路由存在多级路由资源丢失的问题,解决方法有3
- 使用绝对路径
- 使用 %PUBLIC_URL% 代替绝对路径
- 使用 HashRouter
但是 HashRouter 会造成 刷新后 state 参数的丢失,后面会讲
路由模糊匹配与精准匹配
Link 组件的 to 路径会从右往左进行模糊匹配,如果匹配上了会展示模糊匹配的组件,如果从左开始没命中则不展示。
如果要使用精准匹配在 Route 组件中使用 exact 属性开启精准匹配
<Route exact></Route>
路由重定向 Redirect 兜底组件
<Route path="/home" component={Home}>
<Route path="/about" component={About}>
<Redirect to="/about">
这个组件指如果所有的Route组件都匹配不上则走重定向组件
多级路由/嵌套路由
一级路由Link完成注册以及Route准备好展示之后,需要在子页面上继续注册和准备展示。
路由传参
路由传递 params 参数
需要在 Link 在 to 用模板字符串传递参数,然后在 Route 的 path 用冒号占位
<Link to={`/home/msg/${x1}`}></Link>
<Route path="home/msg/:id"/>
然后在路由组件身上的 this.props.match 上就会找到 params
传递 search 参数
传递 search 参数则无需在 Route 组件上声明接受,直接去组件身上的
this.props.location.search
找
<Link to={"home/msg?id=1"></Link>
这种 urlencoded 参数可以借助库来拆解为对象或字符串,例如 node 自带的 qs 库
传递 state 参数
传递 state 参数,Link 组件里的 to就要写成对象形式
<Link to={{
pathname:'/home/message',
state:{
id:'1',
name:'ming'
}
}}></LInk>
然后去组件的 this.props.location.state
上取,这种参数在 BrowserRouter 方式的路由上刷新不会丢失,清空浏览器历史记录才会丢失
push 与 replace
路由开启 replace 模式,只需要在 Link 组件中声明 replace 属性即可。
<Link replace></Link>
或
<Link replace={true}></Link>
编程式路由
在 Link 组件里面的元素,身上的 props 都具有路由组件的方法,调用这些方法可以实现编程式路由
this.props.history.push()
和 this.props.history.replace()
传递 params 参数和 search 都是一样的写法,传递 state 参数则是直接在地址后面接一个对象
this.props.history.push('/home/msg',{id,name})
然后 this.props.history.go() goBack() goFoward() 就是前进后退了
一般组件使用编程式路由导航 withRouter
使用 withRouter 包住一般组件可以让一般组件也有 history,location 这些东西
import {withRouter} from 'react-router-dom'
class MyComponent extends Component{}
export default withRouter(MyComponent)
状态管理 redux
redux 三大核心概念:action creators,store,reducers
action
action就是个对象,包含两个属性:
- type:值为字符串,必要属性
- data:任意类型,可选属性
{type:'ACT_ADD',data:{name:'ming'}}
store.dispatch() 分发action
reducer
- 用于初始化状态和加工状态
- 加工时,根据旧的 state 和 action,产生新的 state 纯函数
- reducer 函数会接到两个参数,分别为之前的状态和动作对象
store
组件中读取 store 的数据使用 getState() 方法
创建 redux/store.js
import { createStore } from "redux";
// import { legacy_createStore as creatStore} from "redux";
import countReducer from './count_reducer'
export default createStore(countReducer)
创建 reducer
const initState = 0
export default function countReducer(preState = initState, action) {
console.log(preState);
const { type, data } = action
switch (type) {
case 'increment':
return preState + data
case 'decrement':
return preState - data
default:
return preState
}
}
组件中使用store
import store from '@/redux/store'
//读取用 getState()
//分发action用dispatch
注意 redux 只管理状态不更新页面,使用 store.subscribe(()=>{}) 即可,只要redux状态变化,就会调用回调
componentDidMount(){
store.subscribe(()=>{
this.setState({})
})
}
或者直接在 index.js 使用
store.subscribe(()=>{
ReactDOM.render(<App/>,document.getElementById('root'))
})
创建专门文件管理 action
redux/xxx_action.js
export const createIncrementAction = (data)=>{
return ({
type:'increment',
data
})
}
创建专门文件管理redux变量常量
redux/constant.js
export INCREMENT = 'increment'
异步 action
import store from "./store"
export const asyncPlus = (data) => {
return () => {
setTimeout(() => {
store.dispatch({ type: 'increment', data })
}, 500)
}
}
借助 redux-thunk 中间件实现
在store.js里
import {createStore,applyMiddleware} from 'redux'
import thunk 'redux-thunk'
import countReducer from './count_reducer'
export default createStore(countReducer,applyMiddleware(thunk))
当然异步 action 不是必须的,完全可以自己等待异步任务的结果再去派发同步 action
react-redux
npm i react-redux
react-redux 多了一层 UI组件和容器组件的概念,UI组件不能直接与 redux 进行交互,只能与容器组件通过 props 进行交互,而容器组件才能进行 store.getState() 等操作
创建容器组件
//引入 UI 组件
import CountUI from '@/compoents/Count'
//引入 store
import store from '@/redux/store'
//引入connect用于链接UI组件与redux
import {connect} from 'react-redux'
//a函数作为返回的对象中key就作为传递给UI组件props的key,value就作为传递给UI组件props的value——状态,会接到一个形参是redux传过来的state
function a(state){
return {count:state}
}
//b函数作为返回的对象中key就作为传递给UI组件props的key,value就作为传递给UI组件props的value——操作状态的方法
//b函数能接到redux传过来的dispatch。返回的函数接到形参是UI组件传过来的数据data
function b(dispatch){
return {jia:(number)={
dispatch({type:'increment',data:number})
}}
}
//使用 connect()() 创建并暴露一个 Count 的容器组件,第一个括号用于传递状态和操作状态的方法,官方名称叫mapStateToProps 和 mapDispatchToProps
export default connect(a,b)(CountUI)
此时注意写好容器组件后,挂在页面上的就是容器组件而不是UI组件,并且要通过props传递store
import Count from './containers/Count'
import store from '@/redux/store'
export default class App extends Component{
render(){
return (
<div>
<Count store={store}/>
</div>
)
}
}
mapDispatchToProps简写
原写法是像上面那样一个接到dispatch的函数,简写携程一个对象,react-redux自动帮你dispatch
export default connect(
state=>({count:state}),
{
jia:createIncrementAction,
jian:createDecrementAction
}
)
Provider组件
我们在使用容器组件的时候都需要手动传一个 store 进去,如果不传则会报错,这时可以借助 Provider 组件,同时注意 react-redux 不需要自己手动更新 render 了
在 index.js 中
import {Provider} from 'react-redux'
ReactDOM.render(
<Provider store={store}>
<App/>
</Provider>,
document.getElementById('root')
)
combineReducers 管理多个模块状态
最终react-redux容器组件
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { createIncrementAction } from '@/redux/count_action'
class Count extends Component {
render() {
return (
<div>
<h1>当前求和为:{this.props.he}</h1>
<button onClick={this.add}>点我加</button>
</div>
)
}
add = () => {
this.props.jiafa(1)
}
}
export default connect(
state => ({
he: state,
}),
{
jiafa: createIncrementAction
}
)(Count)
setState 拓展
setState 写法一
this.setState()会接受两个参数,第一个参数是状态改变对象,第二个参数是状态更新视图也女性g之后会调用的回调
this.setState({num:1},()=>{
console.log(this.state.num)
})
console.log(this.state.num) //这里是读不到的
因为react更新视图是在异步队列进行的,如果在 setState 后面直接读取一些信息会读不到
setState 写法二
写法二的第一个参数写成一个函数,该函数会接到两个形参,state和props。
this.setState((state,props)=>{
return {num:state.num+1}
},()=>{
})
如果新状态依赖于原状态,推荐使用写法二函数式。
第二个参数是一个回调函数,该回调会在 state 更新完后执行。
setState 其他说明
一次函数中多次调用 setState,只会出发一次重新渲染
路由懒加载
从 react 上引入 lazy 使用,需要搭配 Suspense 指定懒加载替换组件,替换组件不能懒加载
import React,{Compoent,lazy} from 'react'
Hook/Hooks
useState
react 16.8 之前,函数式组件没有 state,refs 等,函数式组件连自己的this都没有。有了 hooks 后,函数式组件就可以做很多东西了。但同时注意,函数式组件的render要放最后,不然会出现奇怪的bug。
import React from 'react'
export default function Count() {
// const [状态名,修改状态的方法] = React.useState(初始值)
const [count, setCount] = React.useState(0)
function add() {
// setCount(count + 1)
setCount((value) => {
return value + 1
})
}
return (
<div>index{count}
<button onClick={add}>+</button>
</div>
)
}
修改状态的方法可以接受两种参数,第一种就是直接写值,第二种写成回调函数形式,形参可以接收到原来的值
useEffect
React.useEffect(()=>{},[])
useEffect 可以接收到两个参数,第一个参数是回调函数,该回调会在第二个数组里监测的数据改变后执行,第二个参数是数组,里面的数据会影响第一个参数的回调函数执行。
如果不写第二个参数,就相当于监视所有数据的变化。
使用 effect hook 模拟生命周期
当第二个参数的数组里不写东西,就相当于写了一个 componentDidMount 生命周期。
当第一个参数返回一个函数,这个返回的函数就相当于一个 componentWill
useRef
类似 createRef
import React from 'react'
export default function Count() {
// const [状态名,修改状态的方法] = React.useState(初始值)
const myRef = React.useRef()
const logVal = () => {
console.log(myRef.current.value);
}
return (
<div>
<input type="text" ref={myRef} />
<button onClick={logVal}></button>
</div>
)
}
Fragment 与 空标签
组件必须有一个根标签,此时可以借助 Fragment 组件或根标签来解决
但是注意 Fragment 除了key以外不能写其他属性,空标签则不能加其他属性
context
创建 Context 容器对象:const XxxContext = React.createContext()
渲染子组件时,外面包裹 xxxContext.Provider,通过 value 属性给后代组件传递数据:
<XxxContext.Provider value={数据}>
子组件
</XxxContext.Provider>
后代组件读取数据:
//第一种方式:类似逐渐
static contextTyoe = XxxContext
this.context
//第二种方式,函数组件与类组件都可以
<xxxContext.Consumer>
{
value=>(
要显示内容de
)
}
<xxxContext.Consumer>
通常开发不会使用context,只会用来开发插件。
注意是 React.createContext() 调用后取到的组件,不然会被报错卡住
import React, { useState } from 'react'
const { Provider, Consumer } = React.createContext()
export default function Hello(props) {
const [count] = useState(2)
return (
<Provider value={count}>
<Acompo />
</Provider>
)
}
const Acompo = (props) => {
return (
<Bcompo />
)
}
const Bcompo = (props) => {
return (
<Consumer>
{data => (
<>{data}</>
)}
</Consumer>
)
}
PureComponent
Component的两个问题
- 只要执行 setState() 即使不改变状态数据,组件也会重新 render()
- 只要当前组件重新 render() ,就会自动重新 render 子组件(效率低)
想要效率高,那就是只有当组件的 state 或 props 时才重新 render
原因
组件中的 shouldComponentUpdate() 总是返回true,该钩子可以接收到两个形参,nextProps 和 nextState
shouldComponentUpdate(nextProps,nextState){
}
解决方法之一就是重写这个钩子,比较新旧 state 或 props 值,如果变化了才返回 true,没有变化则 false
最佳解决方法 PureComponent
PureComponent 原理也是重写了shouldComponentUpdate,不过注意的是只是对 state 和 props 进行浅比较。
import {PureComponent} from 'react'
export default class MyComponent extends PureComponent{}
renderProps 插槽
如果一个非自闭合组件在标签体内写一般内容,可以在 this.children 上读取到,但如果放进去的是 DOM,同时还要不知道放什么子组件但要给子组件传参时,那么需要借助 render才行
import React, { Component } from 'react'
export default class index extends Component {
render() {
return (
<div>
<A render={(参数) => {
return <B propname={参数}></B>
}} />
</div>
)
}
}
class A extends Component {
render() {
return (
<>
我是A啊 <br />
{this.props.render(参数)}
<br /> 我是A最后
</>
)
}
}
class B extends Component {
render() {
return (
<>
我是b啊
</>
)
}
}
render props 是一种模式,具体怎么用看个人喜欢,也有直接用 props.children 的。本质还是通过函数实现组件通信,插槽通信。此时最好对 children 进行一些校验
复用组件.propTypes = {
children: PropTypes.func.isRequired
}
error boundary 错误边界
错误边界只能用于生产环境中,可以限制错误扩散,不过getDerivedStateFromError不能捕获自己的错误,只能捕获子组件并且只能捕获到生命周期里的错误
import React, { Component } from 'react'
export default class Parent extends Component {
static getDerivedStateFromError(error) {
console.log(error);
return { hasError: error }
}
state = {
hasError: ''
}
render() {
return (
<div>Parent</div>
{this.state.hasError?<h2>当前网络不稳定,请稍后再试</h2>:<Child/>}
)
}
}
配合 componentDIdCatch(){} 钩子统计错误
高阶组件
高阶组件就是一个function,参数是一个没有自己状态与方法的jsx组件只使用 props 的 jsx 组件。
//需要配合高阶组件使用的低阶组件
let lowerMouse = (props) => {
return (
<img src={img} style={{
width: 10,
position: 'absolute',
top: props.y,
left: props.x
}}></img>
)
}
let LowerMouse = withMouse(lowerMouse)
高阶组件一般以 withXXX 命名,我们来看一下他怎么写的:
接受一个组件的函数,在 render 里使用,并且在使用时候传递函数里的组件的state作为 props。
这个函数最终把函数内的组件返回出去,那么接收一个组件作为参数后就可以为低级组件包装了。
export default function withMouse(WrappedComponent) {
class index extends Component {
state = {
x: undefined,
y: undefined
}
handleXY = (e) => {
this.setState({
x: e.clientX,
y: e.clientY
})
}
componentDidMount() {
window.addEventListener('mousemove', this.handleXY)
}
componentWillUnmount() {
window.removeEventListener('mousemove', this.handleXY)
}
render() {
return (
<WrappedComponent {...this.state} {...this.props}></WrappedComponent >
)
}
}
//优化 设置displayName
index.displayName = `WithMouse${getDisplayName(WrappedComponent)}`
return index
}
function getDisplayName(WrappedComponent){
return WrappedComponent.displayName || WrappedComponent.name || 'Component';
}
优化
- 设置displayName优化开发者工具
- 添加 props
组件性能优化
组件性能优化
减轻state:只存储跟组件渲染相关的数据,不做渲染的数据放在this上即可,例如定时器的id
React18
ReactDOM.createRoot
将原生DOM作为参数传入该方法,调用后返回值就可以挂载虚拟DOM,参数内的所有内容都会被清空
const div = React.createRoot(document.getElementById('root'))
root.render(<App/>)
react-dom/client
18之后引入 ReactDOM 不是从 react-dom 上引入而是
import ReactDOM from 'react-dom/client'
useState()
需要一个值作为参数作为该state的初始值,该函数会返回一个数组,
数组第一个元素是初始值,直接修改不会出发组件的重新渲染。
数组的第二个元素是一个函数,通常命名为setXXX,用来修改state,调用其修改state后会触发组件更新。
该函数的参数就会作为新的值赋值给该state
import {useState} from 'react'
let result = useState(1)
let count = result[0]
let setCount = result[1]
setCount()
当 setState 调用的时候使用旧的state值一定要注意,很可能会出现计算错误的情况,因为更新 state 是异步进行的。这种时候可以使用回调函数来进行setState,回调函数会接到原来的数值作为参数,返回值将作为新值
setCount((preValue)=>{
return preValue+1
})
useRef()
该钩子只能在函数组件中直接使用,不要在嵌套的函数中使用
const myRef = useRef()
return(
<input ref={myRef}/>
)
注意读出组件是从 myRef.current 上读不是直接读 myRef。
其实可以直接写一个普通对象代替 useRef()const myRef = {current:undefined}
不过普通对象会在组件每次重新渲染时创建一个新对象,但 useRef 不会