cdn 初识react
- 先下载 react react-dom (npm install react react-dom)
- 引入并使用
<body>
<div class="test">
</div>
<script src="./node_modules/react/umd/react.development.js"></script>
<script src="./node_modules/react-dom/umd/react-dom.development.js"></script>
<script>
const res=React.createElement("h1",null,"hello react")
//第一个参数为标签名 第二个为标签属性{id:test,class:test} 第三个及以后为子元素 ReactDOM.render(res,document.getElementsByClassName("test")[0])
//第一个为创建的react元素 第二个是容器
</script>
react脚手架 初识
- npx create-react-app my-app (创建项目 npx相较于npm有避免全局污染,减少存储空间等优点)
create-react-app就是react脚手架 - cd my-app / npm start
可以发现my-app里面的src/index.js 里的内容和CDN方式有相似之处 先导入react/react-dom 然后再创建虚拟dom 之后渲染
认识jsx
jsx是在js代码中写xml(html)
在react中使用jsx的目的就是为了简化创建dom的过程
在原生js中是不能直接使用jsx语法的在react脚手架中,使用了Babel进行转换
react style样式
import React from 'react';
import ReactDOM from 'react-dom';
import "./index.css" //这里通过类名来改样式
const isloading = false
const showmsg=()=>{
if(isloading){
return <div>loading...</div>
}
return <div>finish...</div>
}
const res =(
<div className='test' style={{backgroundColor:"red",fontSize:"20px"}}> //这个是具体例子 第二个是通过对象来改样式
show...
{showmsg()}
</div>
)
ReactDOM.render(res,document.getElementById("root"))Te
react组件
react 有俩种形式组件,一种是函数式,一种是类式组件
函数式组件又称为无状态组件,类式组件又称为有状态组件
function Test(){
return (<div>test</div>)
}'
ReactDOM.render(<Test/>,document.getElementByID("root"))
class Test extends React.component{
render(){
return(<div>test</div>)
}
}
react 绑定(添加)事件
on+click = onClick
on +mouseover = onMouseOver
...
function Ho(params) {
function show(){
console.log('show2');
}
return(<div onClick={show}>eee</div>)
}
ReactDOM.render(<Ho/>,document.getElementById("root"))
class Res2 extends React.Component{
show(params) {
console.log('show');
}
render(){
return (
<div onClick={this.show}>123</div>
)
}
}
state 和setstate
state = {count:0,name:'zql'}
addcount = ()=>{
this.setState({
count:this.state.count+1
})
}
render(){
return (
<div>
<div onClick={this.show}>123{this.state.count}{this.state.name}</div>
<button onClick={this.addcount}>count++</button>
</div>
)
}
受控组件
什么是受控组件,就是其中的状态受到state的管理
比如input的value可以设为state中的值,改变时通过onChange监听用setState来改变值
非受控组件
一般通过ref来获取
首先创建 myref = React.createRef()
然后绑定<input ref={this.myref} value="123"></input>
之后获取数据
<button onClick={this.handler5}> get</button>
handler5= ()=>{
console.log(this.myref.current.value);
}
props 传值
函数组件接受用参数 该参数为对象
类组件接受用this.props 也是对象
props是只读对象,在类组件中的constructor函数要使用props,得:
constructor(props){
super(props)
console.log(props);
}
function Test(props) {
return (
<div>
name:{props.name}
age:{props.age}
</div>
)
}
class Test2 extends React.Component{
render(){
return(
<div>
name:{this.props.name}
age:{this.props.age}
</div>
)
}
}
ReactDOM.render(<Test2 name="zql" age="18"/>,document.getElementById('root'))
组件传值 通信
- 父传子通过props 类似上面的
- 子穿父
由于父组件要拿到子组件的值 so父组件的提供一个渠道让子组件将值传递给父组件
一般 通过父组件提供的函数传递给子组件 子组件在合适的时候调用该函数 通过传递参数从而达到传参的效果
class Test extends React.Component{
state={
msg:"msg from small component"
}
postmsg=()=>{
this.props.getmsg(this.state.msg)
}
render(){
return (
<div>
<div>i am the small</div>
<button onClick={this.postmsg}>post to big</button>
</div>
)
}
}
class Test2 extends React.Component{
getMsgFromSmall=(msg)=>{
this.setState({msg})
}
state={
name:"zql",
age:"12",
msg:""
}
render(){
return(
<div>
i am the big one
msg:{this.state.msg}
<Test getmsg={this.getMsgFromSmall}></Test>
</div>
)
}
}
- 兄弟组件之间值的交流
思想主要是状态提升,将所使用的值提升到父组件,父组件提供操作该值的方法。
4.祖->孙组件之间值的交流
思想主要是借助 React.createContext()
通过解构获得俩个组件 const {Provider,Consumer} =React.createContext()
一般将要传递的数据这要搞
<Provider value={this.state}> //value 包含了要传递的值
<div>
i am the big one
msg:{this.state.msg}
<Test getmsg={this.getMsgFromSmall}></Test>
</div>
</Provider>
接受用到
<Consumer>
{data=>console.log(data)}
</Consumer>
5.消息发布订阅机制 PubSub
import PubSub from 'pubsub-js'
PubSub.publish("ABC",{name:'zql',age:22}) //发布消息
this.token=PubSub.subscribe("ABC",function(_,data){ //订阅消息
console.log(data);
})
PubSub.unsubscribe(this.token)//一般在组件卸载时要取消订阅
props.children
如果组件内有东西。那么该组件props对象就有一个children属性
prop-types
对于组件来说 通过props 得到的数据是外来数据,因此很有必要对这些数据做一些规范性限制
要给组件props加规范的这样搞
npm install prop-types
App..propTypes={
name:PropTypes.string,
data:PropTypes.array
}
常见props限制:
PropTypes.string.isRequired 表字符串且必传
PropTypes.shape({
name:PropTypes.string,
age:PropTypes.array.number
})
另外有些情况下需要给props默认值
App.defaultProps={
age:"19"
}
react生命周期
生命周期指组件被创建到被卸载的时间,在这期间有很多函数可以被调用,这就是钩子函数
生命周期概念是在类组件的基础上谈论的,函数组件没有生命周期,因为本质是一个函数没有继承React.Component
react生命周期分为 创建时,更新时,卸载
创建时执行顺序为:
constructor(props){
super(props)
console.log('constructor');
//组件创建时执行 一般在这里做一些初始化操作
}
render(){
console.log('render');
return(
<div>test</div>
)
// 组件每次更新时执行 不要在这里执行this.setState
}
componentDidMount(){
console.log('didmount');
//组件挂载完时执行 一般可在这里操作dom,或网络请求
}
更新时
render(更新时/更新的过程中)->componentDidUpdate(更新完毕)
出发更新的情况:
1 this.setState 2 this.forceUpdate 3 传入新的props
render在数据变化时,重新渲染会调用,componentDidUpdate在更新完后调用,在这里可以操作dom (发送网络请求,更改state)这俩个要加判断,不然会递归 ,一般判断prvprops 是否和当前一样
卸载时
componentWillUnmount(){
console.log('unmount');
}
一般在这一阶段做清除工作,比如清除定时器等
高阶组件(render-props模式)
这里的高阶组件定义为:状态管理复用组件
就是封装的组件提供状态,操作状态的方法,但并不提供dom
使用该组件时,需要提供一个函数
class Test2 extends React.Component{
state = {x:0,y:0}
handler=(e)=>{
this.setState({x:e.clientX,y:e.clientY})
}
componentDidMount(){
window.addEventListener("mousemove",this.handler)
}
render(){
return this.props.render(this.state)
}
}
class Test extends React.Component{
render(){
return(
<div>
test
<Test2 render={(mouse)=>{
return (
<p>x:{mouse.x} y:{mouse.y}</p>
)
}}></Test2>
</div>
)
}
}
比较难理解 但却是很好用 使用test2时能自己决定dom结构 很灵活
这个方式也可以用children来使用
<Test2>{(mouse)=>{return (<p>x:{mouse.x},y:{mouse.y}</p>)}}</Test2>
render(){
return this.props.children(this.state)
}
react 高阶组件(HOC)
这个思想类似于将你的组件经过函数处理之后得到另外的组件
例如将test 组件经过封装后得到res组件 res结构是 test的 但res添加上了数据
具体步骤就是:
定义一个函数 参数是组
函数类创建一个类组件,在该组件做state赋值,和管理state ;render里面返回<参数组件 {...this.state} / >
函数最后返回该类组件
import { func } from "prop-types";
import React from "react";
import ReactDOM from "react-dom"
import "./index.css"
function withMouse(WarpC) {
class Mouse extends React.Component{
state={x:0,y:0}
handle=(e)=>{
this.setState({x:e.clientX,y:e.clientY})
}
componentDidMount(){
window.addEventListener("mousemove",this.handle)
}
componentWillUnmount(){
window.removeEventListener("mousemove",this.handle)
}
render(){
return <WarpC {...this.state}/>
}
}
return Mouse
}
function Test(props) {
return(
<div>{props.x}{props.y}</div>
)
}
const Res = withMouse(Test)
ReactDOM.render(<Res/>,document.getElementById('root'))
setState
this.setState 是异步更新
count:1
// this.setState({count:this.state.count+20})
console.log(this.state.count);
// this.setState({count:this.state.count+10})
console.log(this.state.count);
上面代码先执行俩个log 输出1,1.之后会执行俩个setState() 最后第二个会覆盖第一个的结果 count:11
通过以下如果想避免这种:
可这种
console.log(state,props);
return {
count:state.count+1
}
})
console.log(this.state.count);
// this.setState({count:this.state.count+10})
this.setState((state,props)=>{
console.log(state,props);
return {
count:state.count+1
}
})
console.log(this.state.count);
同样会先执行log 但第一个setState count:2 ;第二个setState:count:3
this.setState({},()=>{
这里是更改完后,渲染完后执行的回调
})
组件性能优化
react 组件的更新会更新自身和子树组件。这样如果更新root组件,即使子组件没更新也会重新渲染。
因此有必要做些优化
- 减少state的体积。state里面只放和渲染有关的变量,其余比如setIntView..的timerID放在组件实例身上即可。
- shouldComponentUpdate
shouldComponentUpdate这个钩子函数在render之前执行,可根据函数返回值来判断是否更新
shouldComponentUpdate(nextProps,nextState){
return JSON.stringify(this.state) == JSON.stringify(nextState) ? false : true
} - 使用纯组件
原来创建组件使用 extends React.Component 这种方式要我们手动添加shouldComponentUpdate()钩子来优化 ,但 如果是这样 class App extends React.PureComponent这样就自动比较 state , props
但这里有个坑,在比较引用对象时,比较的是地址,因此修改引用对象时应该新建
比如:{...{this.state.obj},name:"zql"} [...this.state.arr,{data}]
虚拟DOM 和 diff算法
组件通过render 或直接返回的jsx 其实和React.createElement()效果都是创建的虚拟dom 其本质是一个js对象,有type属性,props属性。每次调用setState都会创建新的虚拟dom,然后通过diff算法对比新旧虚拟dom,找到不同的地方然后在真实Dom打补丁即可
虚拟dom配合diff算法确实可以改善性能 , 但虚拟dom最强大的作用是虚拟dom本质是js也就是说任何支持js代码的地方能运行虚拟dom,这也是react能在服务器端,iOS,Android端跑的原因。摆脱了浏览器的限制,实现跨平台
react路由
首先先下载 npm install react-router-dom
之后如下操作:
import React from "react";
import ReactDOM from "react-dom"
import PropTypes from "prop-types"
import "./index.css"
import {BrowserRouter as Router,Route,Link,Routes} from "react-router-dom"
const Test2 = ()=>{
return (
<p>ppp</p>
)
}
const Test=()=>(
// 用Router将整个应用包裹起来
<Router>
//设置路由入口
<Link to="/first">first</Link>
//设置路由出口
<Routes>
<Route path="/first" element={<Test2/>}></Route>
</Routes>
</Router>
)
ReactDOM.render(<Test />,document.getElementById('root'))
编程式路由 this.props.history.push('/path')
this.props.history.go(-1)
路由版本不同有些不一样
路由默认采用模糊匹配模式(当url中的地址事宜route path 就能匹配)
to=/a/b/c 能被 path=/a命中 to的路径要以path开头才能命中
给 Route组件添加exact属性即可精确匹配
Navlink
import {NavLink} from "react-router-dom"
<NavLink to="/path">path</NavLink>
这种选中就会添加一个className="active"
可以自定义改类名:activeClassName
如果有多个<Route>组件,匹配时会全部匹配,可以使用<Switch>组件,只匹配第一个
重定向;默认;兜底;一般写在所有路由最下方
<Redirect to="/home"/>{/* 默认 */}
向路由组件传参
- params 方式
<Link to={/home/${this.state.id}/${this.state.msg}}>
<Route path="/home/:id/:msg" component={Test}>
这样Test组件就可以在this.props.match.params里面拿到数据了 - search方式
<Link to={/home/?id=${this.state.id}&msg=${this.state.msg}}>
<Route path="home" component={Test}>
之后Test组件可在this.props.location.search 拿数据 - state方式
<Link to={{pathname:"/home"},state={id:this.state.id,msg:this.state.msg}}>
<Route path="/home"} component={Test}/>
Test组件可以在this.props.location.state拿到数据
路由补充
<Link to="/home">aaa<Link/>这种默认是push模式
<Link to="/home" replace>aaa<Link/> replace模式
一般组件的props是没有路由组件props的history等api
将一般组件弄成路由组件可用withRouter函数
import {withRouter} from "react-router-dom"
export default withRouter(App)
编程方路由导航
普通 this.props.history.push("/home") this.props.history.replace("/home")
1.params模式:
this.props.history.push(/home/${id}/${msg})
2.search模式
this.props.history.push(/home/?id=${id}&msg=${msg})
3.state模式
this.props.history.push('/home',{id:id,msg:msg})
this.props.history.go(-1)返回
redux
redux 是集中状态管理的地方,可用在三大框架上面。redux有三个重要组成:action,reducer,state
action:本质是js对象,其中必有type属性;组件通过发送action到store,让store交给reducer加工action,返回state(数据)给store;之后组件可通过store拿到新数据;
store:接收action;判断action是否合法;将action转发reducer;接收reducer返回的数据
reducer:本质是一个函数,参数有preState,action;返回新的state
原理图


hooks
有类式组件了,为毛需要函数组件
官方说了三点:
- 类组件之间复用状态逻辑困难;需要借助高阶组件<provider><consumer> HOC 或者 render props等;函数组件将状态逻辑抽离出来;对于开发人员很友好
- 复杂组件难以理解;在类组件中,每个生命周期函数可干很多事,例如componentdidmmount,componentdidupdate都需要加载数据;那么这是代码复用率低,使用hooks useeffect可解决服用问题
- class理解;js中也有class的概念,react class组件,这俩者难免有冲突的地方
我自己理解:class 本就是function 的语法糖;因此使用function理论更快
facebook 推荐使用hooks,但也保留class的维护;其本身也用了很多class 组件;
useState
useState 在函数组件内部调用;产生俩个之,一个是state数据,一个是改变state的函数;可通过该函数改变其值,类似this.setState();区别在于不会想setState合并;
【注】组件会保留该state和操作state的函数 重复渲染时依然用原来的;初始 state 参数只有在第一次渲染时会被用到
import {useState} from "react"
function App() {//
const [count,setCount] =useState(0)
//使用useState hook后会产生一个数组,第一个元素为数据,第二个为操作该数据的函数,该函数为异步执行,下面代码会先执行log语句,改变执行顺序可这样写
{setCount(count=>{
count++
log(count)
return count
})}
const handle=()=>{
setCount(count+1)
console.log(count);
}
return (
<div>
count:{count}
<hr />
<button onClick={()=>handle() }>count++</button>
</div>
);
}
export default App;
useState((preState)=>{})
useState参数如果是函数,那么该函数的参数就是prev数据,如果该函数没返回值,那么下一次prev数据就为undefined;
reducer
当我们的数据为引用数据时,最好用useReducer
import { useState,useReducer } from "react";
function App(params) {
const reducer = (state,action)=>{
switch (action.type) {
case "add":
return {...state,count:state.count+1}
break;
case "jian":
return {...state,count:state.count-1}
default:
return state
break;
}
}
const [state,stateDispatch] = useReducer(reducer,{count:0,name:'zqk'})
// useReducer执行时,将第二参数传给第一reducer的state作为值,action为undefined,执行后得到一个数组,第一个为返回的值,第二个为操作改值的函数。类似redux。
const handle=()=>{
stateDispatch({type:"add"})
}
return(
<div>
count:{state.count}----name:{state.name}
<hr />
<button onClick={handle} >count++</button>
</div>
)
}
export default App
副作用
副作用就是用到了外部变量就会产生副作用,比如用到了全局变量,就会有修改它的副作用,比如操作dom,网络请求,document.title=''等等
useEffect
场景一
useEffect(()=>{
console.log('a')
})
将函数作为参数传入该函数,这个函数就会在组件被挂载后执行,当页面有更新也会执行
场景二
useEffect(()=>{
console.log('a')
},[])
将函数作为参数传入该函数,并且将空数组作为第二个参数,这个函数就会在组件被挂载后执行,但是更新时不会执行
场景三
useEffect(()=>{
console.log('a')
},[a,b])
将函数作为参数传入该函数,将[a,b]作为第二个参数传入,这个函数就会在组件被挂载后执行,当变量a,b更新也会执行
场景四
useEffect(()=>{
console.log('a')
return ()=>{
console.log('b')
})
将函数作为参数传入该函数,这个函数就会在组件被挂载后执行,当页面有更新也会执行,该函数返回值是函数,当页面有更新,或者页面被卸载都会执行
场景五
useEffect(()=>{
console.log('a')
return ()=>{
console.log('b')
},[])
将函数作为参数传入该函数,将[]作为第二个参数,这个函数就会在组件被挂载后执行,当页面有更新不会执行,该函数返回值是函数,当页面有更新不会执行,页面被卸载会执行
场景六
useEffect(()=>{
console.log('a')
return ()=>{
console.log('b')
},[a])
将函数作为参数传入该函数,将[a]作为第二个参数,这个函数就会在组件被挂载后执行,当a有更新会执行,该函数返回值是函数,当a有更新会执行,页面被卸载会执行
匹配到[a,b...]的更新时,首先会看是否有返回函数,如果过有就先执行它,在执行外函数
函数组件父子传值
父传子和类组件一样
<Test a={1} b={2}/>
function Test(props){...}//接收就使用props
子传父也是类似的
父元素提供函数接口,子组件在合适时候调用函数,将数据通过参数传给父组件。
import { useState, } from "react";
function Test(props) {
const [name,setName]=useState("dym")
return(
<div>
test...
<hr />
name:{props.name}
age:{props.age}
<hr />
myname:{name}
<hr />
!!!!!!!!!
<button onClick={()=>{props.test('abc')}}>btn</button>
</div>
)
}
function App(params) {
const [count,setCount]=useState(0)
const [name,setName] =useState("")
const handle=(msg)=>{
setName(msg)
}
return(
<div>
msg from son :
count:{count} --myname={name}
<hr />
<button onClick={()=>setCount(count+1)}>count++</button>
<hr />
<Test name="zql" age="33" test={handle}></Test>
</div>
)
}
export default App
useContext
祖孙组件传递用这个
import { useState,useContext ,createContext} from "react";
const Context = createContext() //如果组件不在同一文件,记得将它导出,子孙组件用时,导入即可
function Test(props) {
return(
<div>
test
<hr />
<Test2></Test2>
</div>
)
}
// 孙
function Test2(params) {
const count = useContext(Context)
return (
<div>
test2--count:{count}
</div>
)
}
//root
function App(params) {
const [count,setCount]=useState(0)
return (
<div>
app--count:{count}
<button onClick={()=>setCount(count+1)}>btn</button>
<hr />
<Context.Provider value={count}>
<Test/>
</Context.Provider>
</div>
)
}
export default App