react基础教程

基础

什么是react

react是构建用户界面的js库,vue是前端框架,vue具有侵入性,侵入性的意思是指规定一种方式,你要按照他的方式去写代码

react的特点

  • 轻量:react的开发版的所有代码(包括注释)仅有3000多行
  • 原生:所有的react的代码基本都是用原生js书写的,不依赖其他的库
  • 易扩展:react对代码的封装程度比较低,没有使用过多的魔法,所以react中的很多功能都可以扩展
  • 不依赖宿主环境:react只依赖原生js语言,不依赖其他任何东西,包括运行环境,因此,它可以轻松的被移植到浏览器端,移动端
  • 渐近式:react并非框架,可以对已经存在的工程逐步改造成react
  • 单项数据流:数据都是自顶向下的
  • 用js代码声明界面
  • 组件化

对比vue

对比项vuereact
全球使用量
国内使用量
性能
易上手
灵活性
大型企业
生态

学习路径

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 : 快速代码编写
  • 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>
    )
}

导航守卫

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值