基础
什么是react
react是构建用户界面的js库,vue是前端框架,vue具有侵入性,侵入性的意思是指规定一种方式,你要按照他的方式去写代码
react的特点
- 轻量:react的开发版的所有代码(包括注释)仅有3000多行
- 原生:所有的react的代码基本都是用原生js书写的,不依赖其他的库
- 易扩展:react对代码的封装程度比较低,没有使用过多的魔法,所以react中的很多功能都可以扩展
- 不依赖宿主环境:react只依赖原生js语言,不依赖其他任何东西,包括运行环境,因此,它可以轻松的被移植到浏览器端,移动端
- 渐近式:react并非框架,可以对已经存在的工程逐步改造成react
- 单项数据流:数据都是自顶向下的
- 用js代码声明界面
- 组件化
对比vue
对比项 | vue | react |
---|---|---|
全球使用量 | √ | |
国内使用量 | √ | |
性能 | √ | √ |
易上手 | √ | |
灵活性 | √ | |
大型企业 | √ | |
生态 | √ |
学习路径
1.react基础知识
2.react-router
3.redux
4.react源码
使用脚手架
官方脚手架:create-react-app
搭建脚手架的时候使用的是 yarn create react-app 项目名字
create会将脚手架补全,虽然是react-app 实际还是create-react-app
环境的配置
- vscode 的配置
- emmet 配置 json
emmet.includeLanguages:
'javascript':"javascriptReact"
这个设置是为了能在文件中打出div就可以出现<div></div>
- vscode插件安装
- ESLint 代码风格检查
- ES7 React/Redux/GraphQL/React-Native snippets : 快速代码编写
- emmet 配置 json
- Chrome插件
- react developer tools
jsx
<></>
相当于<React.Fragment></React.Fragment>
key可以写在<React.Fragment>上面,不能写在<></>上面- null undefined false不会显示
const obj={"a":"1"}
<div>{ obj }</div>
会报错- 其他:
cosnt list=new Array(30) ;
list.fill(0)
//list是一个长度为30的数组,数组的元素都是0
组件和属性,状态
- 函数组件:属性是直接作为参数传递给函数
- 类组件:属性作为参数传递给构造函数 ,构造函数里面要写 super(props),因为是继承的,所以要先调用父类的构造函数,super(props)相当于 this.props=props; 类组件在使用属性的时候,可以不用写构造函数,使用用this.props就可以
- 属性是别人传递过来的,使用的组件不能更改这个值
- 状态是自己的,自己可以更改
setState
setState方法是绑定在某个html元素的事件里面的时候,是异步的,
//n初始化的值为0
handelClick(){
this.setState({
n:this.state.n+1
})
console.log(n);//此时打印出来的结果是0,还没有重新渲染页面
}
handelClick(){
this.setState({
n:this.state.n+1
},()=>{
console.log(n);//此时打印出来的结果是1
})
}
想用之前的状态值,可以在setState方法的第一个参数传入一个函数
handelClick(){
this.setState({
n:this.state.n + 1 // 0+1
})
this.setState({
n:this.state.n + 1 //0+1
})
this.setState({
n:this.state.n + 1 //0+1
})
}
//每点击一次,页面上面的n只会增加1,而不是3.因为每次拿到的this.state.n没有变化
handelClick(){
this.setState((curState)=>{
//curState 表示的是当前的状态值
//该函数的返回结果会覆盖掉之前的状态
//函数的异步的
return {
n:curState+1
}
})
this.setState((curState)=>{
//curState 表示的是当前的状态值
//该函数的返回结果会覆盖掉之前的状态
//函数的异步的
return {
n:curState+1
}
})
this.setState((curState)=>{
//curState 表示的是当前的状态值
//该函数的返回结果会覆盖掉之前的状态
//函数的异步的
return {
n:curState+1
}
})
//页面上没有点击的,n增加3
}
类型检查
import PropTypes from ‘prop-types’;
MyComponent.propTypes = {}
给props默认值 MyComponent.defaultProps = {“name”:“ewew”}
HOC
高阶组件是传入的参数是组件,返回一个新的组件
高阶组件主要用来解决横切关注点的问题
举例:假如有20个组件,每个组件在创建或者销毁的时候,都要进行日志记录,这个时候就用到了高阶组件,把日志记录部分放到高阶组件里面
注意以下两点:
- 不要在render里面使用高阶组件,因为每次都创建新的组件,导致每次都要重新挂载,卸载
- 不要更改传入的组件,因为这个传入的组件也可能被其他高阶组件使用
export function A() {
return (
<div>
A
</div>
)
}
export function B() {
return (
<div>
B
</div>
)
}
export default function withLog(Comp,str) {
return function Wrapper(){
console.log("组件的名字是:",Comp.name)
return <Comp></Comp>
}
}
export default function App (){
const Alog=withLog(A,"ddddddd");
const Blog=withLog(B);
return (
<div>
<Alog></Alog>
<Blog></Blog>
</div>
)
}
ref
class组件 ref=React.createRef()
函数组件 ref=useRef();
- 当ref在真实的元素上的时候,ref.current得到的是真实的dom元素
- 当ref在组件上的时候,(只能是类组件),得到的是整个组件实例
ref转发
父组件想要得到子组件的某个元素,两种情况
1.要是子组件是一个函数组件的话,可以使用forwardRef
2.要是子组件是类组件的话,把ref当作一个属性传过去
forwardRef方法:
- 传入的参数是函数组件,类组件不可以,函数组件需要两个参数,props,ref
- 返回的是一个新的组件
function B (props,aref){
return(
<div>
<h1 ref={aref}>h11111111</h1>
</div>
)
}
export default function App() {
const NewB = React.forwardRef(B);
const aref = useRef();
return (
<div>
<NewB ref={aref}></NewB>
<button onClick={() => {
console.log(aref,"得到的aref的值B组件的h1")
}}>
click
</button>
</div>
)
}
子组件是class组件
class A extends Component {
constructor(props) {
super(props);
this.textInput = React.createRef();
}
render() {
return (
<div>
<input type="text" ref={this.props.aref} />
</div>
)
}
}
export default function App() {
const ref = useRef();
return (
<div>
<A aref={ref}></A>
<button onClick={() => {
console.log(ref)}}//得到的值是input元素
>click</button>
</div>
)
}
高阶组件里面使用ref
export default function WithLog(Comp) {
class Wrapper extends Component {
componentDidMount() {
console.log(this.props, 666)
}
render() {
const { forwardRef, ...restProps } = this.props;
return <Comp ref={forwardRef}{...restProps}></Comp>
}
}
return React.forwardRef((props, ref) => {
return <Wrapper forwardRef={ref} {...props}></Wrapper>
})
}
export default function App() {
const NewA = withLog(A);
const domRef = useRef();
return (
<div>
<NewA ref={domRef}></NewA>
<button onClick={() => { console.log(domRef, 44444) }}>click</button>
</div>
)
}
纯组件 PureComponent
这种只能用在类组件上
当状态或者属性没有改变的时候,不需要渲染的时候不渲染
class组件继承的时候不在继承Component而是继承PureComponent
React.memo()
函数组件想要使用纯组件的时候,返回React.memo(函数组件)就是一个纯组件
其他
- 项目里面的事件冒泡是根据组件结构,不是真实的dom结构
const obj={
"a":"1111",
"b":"3333"
}
// 想要把obj的b改成"b":"2222",不能写成 this.state.obj.b="2222",正确的处理办法是
this.setState({
...obj ,
"b":"2222"
})
//或者使用 Object.assign()将两个对象混合
this.setState(
Object.assign( {} , this.state.obj , { b :" 2222" })
)
判断:Object.is()
context
创建上下文 createContext,
提供上下文数据 MyContext.Provider,不要重复使用Provider,应该重新创建
使用上下文数据
- 在类组件中 使用 this.context得到数据 static contextType=MyContext; this.context.name
- 在函数组件里面使用Consumer得到数据,他的子节点是一个函数
// 类组件使用上下文
const MyContext = React.createContext();
class Child extends Component {
static contextType=MyContext;
render() {
return (
<div>
<h1>{this.context.name}</h1>
this is child
</div>
)
}
}
class Father extends Component {
render() {
return (
<div>
this is father
<Child></Child>
</div>
)
}
}
export default function App() {
return (
<MyContext.Provider value={{"name":"ewewewewewew"}}>
<div>
<Father></Father>
</div>
</MyContext.Provider>
)
}
//函数组件使用上下文
function Child() {
return (
<MyContext.Consumer>
{ value=>{
return <div>
<h1>{value.name}</h1>
</div>
}}
</MyContext.Consumer>
)
}
render Props
解决横切关注点的问题,将相同的内容提到一个父组件里面,不同的内容在两个子组件里面。子组件需要的东西由父组件提供,提供的方式是:传递一个函数,函数的返回值是要渲染的内容.
和正常的属性是一样的,只是他的属性名称叫做render,使用的时候,this.props.render
//Father.js组件中
<Son1>
(mouse)=>{
return <div>鼠标的位置是{mouse.x},{mouse.y}</div>
}
</Son1>
// son1.js组件中
return (
<div>
{this.props.children(this.state.mouse)}
</div>
)
portals
插槽
将想要渲染的组件插入到想要渲染的位置
ReactDOM.createPortal(child, container) 返回的是一个react元素
function por(){
return ReactDOM.createPortal(
<h1>portalpor</h1>, document.getElementById("root")
)
}
错误边界
错误边界只能捕获子组件发生的同步错误,不能捕获自己的错误,也不能捕获异步的、事件中的错误(可以使用try catch).
class ErrorBound extends Component {
state={
hasError:false
}
static getDerivedStateFromError(error){
return{
hasError:true
}
}
//getDerivedStateFromError或者componentDidCatch都可以,只有子组件发生错误的时候,才会运行这个函数
//大多数情况下getDerivedStateFromError改变状态,componentDidCatch进行错误记录
//getDerivedStateFromError 发生在子组件发生错误之后,渲染页面之前
//componentDidCatch 发生在子组件发生错误之后,渲染页面之后
componentDidCatch(error,errorInfo){
this.setState({
hasError:true
})
}
render() {
if(this.state.hasError){
return this.props.children
}else{
return <h1>have error</h1>
}
}
}
function Child() {
const data = null;
const li = data.map((item, index) => {
return <li key={index}>{item}</li>
})
return (
<div style={{
width: "300px", height: "300px", border: "1px solid red"
}}>
<h1>child</h1>
<ul>
{li}
</ul>
</div>
)
}
class Father extends Component {
render() {
return (
<div style={{
width: "900px", height: "900px", border: "1px solid red"
}}>
this is father
<ErrorBound>
<Child></Child>
</ErrorBound>
</div>
)
}
}
export default function App() {
return (
<div style={{
width: "1000px", height: "1000px", border: "1px solid red"
}}>
<h1>app</h1>
<Father></Father>
</div>
)
}
工具
浏览器方面 react developer tools
Hook知识点
自定义Hook
函数名 命名的时候要以use开头,例如 useGetAllStudent,使用的时候, const stu=useGetAllStudent();
//useGetAllStudent.js
//说明:ajaxGetStudent是得到学生数据的函数,返回学生数组
function getAllStudent() {
const [stu, setStu] = useState([]);
useEffect(() => {
(async ()=>{
const stu=await ajaxGetStudent();
setStu(stu);
})()
})
return stu;
}
useState
- 要是改变的state是一个对象
cosnt [stu,setStu]=useState({"name":"lanlan","age":18})
//改变年龄
setStu({
...stu,
"age":22
})
- 强制刷新:
- 类组件:this.forceUpdate
- 函数组件 :const [ ,forceUpdate]=useState( { } ) ,forceUpdate({})
直接给一个空对象,因为每次对象的地址都不一样
- 改变状态的时候,想要使用之前的状态值,
setN(N-1);
setN(N-1);
// 这个时候的N值只能改变一次,正确的打开方式是传一个函数
setN(pre=>pre-1)
useEffect
- useEffect里面的函数运行的时间点是在真实的UI渲染之后,异步执行,不会堵塞浏览器
- componentDidMount ,componentDidUpdate运行时更新了UI,但是用户还没有看到Ui,同步的,useEffect中的副作用函数执行时已经看到了UI
- useEffect中的副作用函数,可以有一个返回值,这个返回值是一个函数,这个函数叫做清理函数,——》函数运行的时间点是在运行副作用函数之前,首次渲染组件的时候不会运行,组件被销毁时一定会运行
useReducer
function reducer(state, action) {
switch (action.type) {
case "increase":
return state + 1
case "decrease":
return state - 1
default:
return state;
}
}
export default function App() {
const [n, dispatch] = useReducer(reducer, 1);
return (
<div>
<h1>{n}</h1>
<button
onClick={() => { dispatch({ type: "increase" }) }}
>+</button>
<button
onClick={() => { dispatch({ type: "decrease" }) }}
>-</button>
</div>
)
}
useContext
可以减少层级
创建上下文 const context=React.createContext();
使用 : 之前使用的时候,是consumer,拿到的值是通过一个函数。现在的值将上下文对象传给useContext()
const Ctx = React.createContext();
function Son() {
return (
<Ctx.Consumer>
{ value => <h1>{value}</h1> }
</Ctx.Consumer>
)
}
function Son2() {
const value = useContext(Ctx);
return (
<div>{value}</div>
)
}
export default function App() {
return (
<Ctx.Provider value={"dsdsdsds"}>
<Son ></Son>
<Son2 ></Son2>
</Ctx.Provider>
)
}
useCallback
得到固定函数的引用值,就是让一个函数的地址不变化,通常用来优化。例如父组件里面使用了一个子组件,这个子组件的一个属性是一个函数,每次渲染父组件的时候,这个绑定的事件函数都是一个新的函数,所以子组件会跟着更新,要想不更新可以使用useCallback,这个方法有两个参数 :
- 1.需要执行的函数,在第一次执行useCallback的时候,传递函数的地址给handelClick ,第二次执行组件的时候,看依赖项有没有改变,要是没有改变的话,Son组件就不会更新
- 2.依赖项数组
function Parent() {
console.log("parent");
//当只改变input的值的时候,son2组件跟着更新了,说明 onClick属性改变了,每次得到了新的函数
const [txt, setTxt] = useState(123);
const [inputValue, setInputValue] = useState("");
const handelClick = useCallback(() => {
setTxt(txt + 1)
}, [txt])
return (
<div>
{/* <Son onClick={() => { setTxt(Math.random()) }} txt={txt}></Son> */}
<Son onClick={handelClick} txt={txt}></Son>
<input type="number" value={inputValue} onChange={(e) => {
setInputValue(e.target.value);
}} />
</div>
)
}
// function Son(props) {
// console.log("son")
// return (
// <div>
// <h1>{props.txt}</h1>
// <button onClick={props.onClick}>click</button>
// </div>
// )
// }
class Son extends Component {
render() {
return (
<div>
<h1>{this.props.txt}</h1>
<button onClick={this.props.onClick}>click</button>
</div>
)
}
}
export default function App() {
return (
<div>
<Parent ></Parent>
</div>
)
}
useMemo
保持值的稳定
处理需要长时间加载的事情,例如,页面上从1渲染到10000数字,要是不进行处理的话,其他事件渲染的时候,数字也重新渲染,数字渲染的事件要很长时间,这就会导致页面卡顿
useMemo和react.memo没有关系
useMemo接受两个参数,第一个处理函数(这个函数返回一个值),第二个依赖项数组
const list=useMemo(()=>{
const list=[];
for(i=min;i<max;i++){
list.push(i)
};
return list;
},[min,max])
useRef
之前使用ref的方法是const myref=React.createRef();
function App(){
//const myref=React.createRef();
//这样写代码的话,每次重新渲染App组件的时候,都会重新创建一个myref,他们不相等
const myRef=useRef();
//这样的话,即使App重新渲染了,但得到的myRef还是相同的
return()
}
useImperativeHandle
function FancyInput(props, ref) {
useImperativeHandle(ref, () => ({
method: () => {
console.log("ssdsds")
}
}));
return <div>ffsfs< /div>;
}
FancyInput = forwardRef(FancyInput);
在本例中,渲染
<FancyInput ref={inputRef} />
的父组件可以调用 const inputRef = useRef();
inputRef.current.method()。
useLayoutEffect
和useEffect的用法是完全一致的,他要解决的问题是,需要进行大量的dom处理。因为useLayoutEffect他的处理时间点是在用户看到UI之前,useEffect是在用户看到UI之后,要是使用useEffect处理大量的dom操作,页面会闪烁一下
注意:推荐使用useEffect
react的动画库
react-transition-group 后续更新
Router
安装
安装的是react-router-dom
左侧导航右侧展示
.admin-container{
position: fixed;
width: 100%;
height: 100%;
left: 0;
top: 0;
}
.aside-container{
width: 200px;
background-color:#008c8c ;
position: absolute;
top: 0px;
left: 0px;
height: 100%;
}
.content-container{
position: absolute;
padding: 20px;
left: 200px;
right: 0;
top: 0;
bottom: 0;
background-color: beige;
}
需要用到Router ,Route ,Switch,Link
在App组件中,使用Router包裹 Login ,Admin组件
export default function index() {
return (
<Router>
<Switch>
<Route path="/login" component={Login}></Route>
<Route path="/" component={Admin}></Route>
</Switch>
</Router>
)
}
整个Admin页面包含的是 左侧导航和右侧的展示内容
左侧的导航代码
export default function Aside() {
return (
<div className="aside-container">
<ul>
<li>
<Link to="/addStudent">Add Student</Link>
</li>
<li>
<Link to="/deleteStudent">Delete Student</Link>
</li>
<li>
<Link to="/showStudent">Show Student</Link>
</li>
</ul>
</div>
)
}
右侧是内容代码
export default function Content() {
return (
<div className="content-container">
<Switch>
<Route path="/addStudent" component={AddStudent}></Route>
<Route path="/deleteStudent" component={DeleteStudent}></Route>
<Route path="/showStudent" component={ShowStudent}></Route>
</Switch>
</div>
)
}
未授权页面
Route 有一个render属性
render 只有匹配之后才会展示
children 始终展示
页面上有三个子页面,首页、登录页、个人页(这个页面是受保护的页面,只有登陆了才可以看见,)要是想进入个人页面,如果登陆了,正常进入,要是没有登陆,需要跳转到登陆页面,登陆成功之后直接跳转到个人页
正常的页面跳转都是用到Route<ProtectRoute path="/personal" component={Protect}></ProtectRoute> <Route path="/" component={Home}></Route>
但受保护的页面,要是写Route的话,没有办法做判断,所以另写一个ProtectRoute 组件,这个组件根据登陆的状态进行判断,要是已经登陆了,直接展示,展示的方法是 根据Route的render属性,render是一个函数,这个函数的返回值是要展示的内容,这个内容是props.component 组件,要是没有登陆的话,使用props.history.push(’/login’),切换到login页面。
login组件需要处理的是,跳转到受保护页面,可以在ProtectRoute 组件跳转到login页面的时候,使用return <Redirect to={{ pathname:"/login",state:rest.location.pathname} }></Redirect>
这样在login页面就可以获取到个人页的路径名字,然后点击登陆之后,执行if(props.location.state){ props.history.push(props.location.state) }
import React from 'react'
import { Redirect, Route } from 'react-router-dom';
import loginInfo from './loginInfo';
export default function ProtectRoute({render,component:Componenct,children,...rest}) {
if(loginInfo.loginState){
return <Route render={values=>{
return <Componenct></Componenct>
}}></Route>
}else{
console.log(rest)
return <Redirect to={
{
pathname:"/login",
state:rest.location.pathname
}
}></Redirect>
}
}
import React from 'react'
import loginInfo from './loginInfo'
export default function Login(props) {
return (
<div>
login
<button onClick={() => {
loginInfo.loginIn();
if(props.location.state){
props.history.push(props.location.state)
}
}}>Login</button>
</div>
)
}
import React from 'react';
import { BrowserRouter as Router, Route, Switch, Link } from "react-router-dom";
import Login from './Login';
import Home from './Home';
import Protect from './Protect';
import ProtectRoute from './ProtectRoute';
export default function index() {
return (
<Router>
<div>
<Link to="/">to Home</Link>
<div></div>
<Link to="/login">to Login</Link>
<div></div>
<Link to="/personal">to Protect</Link>
</div>
<div>
<Switch>
<Route path="/login" component={Login}></Route>
<ProtectRoute path="/personal" component={Protect}></ProtectRoute>
<Route path="/" component={Home}></Route>
</Switch>
</div>
</Router>
)
}