React从入门到入土

  React学习

React 的特点

  1. 采用组件化的模式,声明式编码,提高开发效率及组件复用率

React创建虚拟dom的两种方式

  1. jsx 创建虚拟dom

    <script type="text/babel">
        // 创建虚拟dom
        const VDOM = (
        <h1 id="title">
            <span>你好,React</span>  
        </h1>
        )
        // 渲染虚拟dom到页面
        ReactDOM.render(VDOM, document.getElementById("test"));
    </script>
    
  2. js 创建虚拟dom 一般不用

      <script type="text/javascript">
        // 创建虚拟dom
        const VDOM = React.creactElemnet("h1",{id:'title'},'你好,React');
        // 渲染虚拟dom到页面
        ReactDOM.render(VDOM, document.getElementById("test"));
      </script>
    

虚拟dom和真实dom

    <script type="text/babel">
        // 创建虚拟dom
        const VDOM = (
            <h1 id="title">
                <span>你好,React</span>
            </h1>
        )
        // 渲染虚拟dom到页面
        ReactDOM.render(VDOM, document.getElementById("test"));
        console.log("虚拟dom", VDOM);
        console.log(typeof VDOM);
        console.log(VDOM instanceof Object);
        /*
          关于虚拟dom:
          1. 本质上是Object类型的对象(一般对象)
          2. 虚拟dom比较轻,真实dom比较重,因为虚拟dom是React内部在用,无需真实dom那么多的属性
          3. 虚拟dom最终会被React转化为真实dom,呈现在页面上
          */
    </script>

jsx 语法规则

    <script type="text/babel">
        const myid = "aTGUgu";
        const mydata = "HeLLodfajslfjlas";
        // 1.创建虚拟dom
        const VDOM = (
            <div>
                <h2 className="title" id={myid.toLowerCase()}>
                    <span style={{ color: 'red', fontSize: '200px' }}>
                        {mydata.toLowerCase()}
                    </span>
                </h2>
                <h2><input type="text"></input></h2>
            </div>
        )
        // 2. 渲染虚拟dom到页面
        ReactDOM.render(VDOM, document.getElementById("test"));

        /**
         * 标签中有js表达式,用花括号包裹,花括号里面可以使用表达式
         * 样式的类名指定不要用class,要用className
         * 内联style使用驼峰命名法
         * 虚拟dom必须只有一个根标签 
         * 标签必须闭合
         * 标签首字母
         *  (1)若小写字母开头,则将改标签转为html中同名元素,若html中无同名元素,报错
         * (2)若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错    
         */
    </script>

React 处理数组

image-20210525110822822

注意:{}里面只能写表达式,不能写代码,如果写代码,必须返回参数

模块与组件

  1. 函数式组件

        <script type="text/babel">
            // 1. 创建函数组件
            function Dome() {
                console.log(this);// 此处的this是undefined ,因为babel编译后开启了严格模式
                return <h2>我是用函数定义的组件(适用于[简单组件的定义])</h2>
            }
            // 2. 渲染组件到页面
            ReactDOM.render(<Dome />, document.getElementById("test"));
        </script>
    

    类的概念

        <script type="text/babel">
            // 创建一个Person类
            class Person {
                // 构造器方法
                constructor(name, age) {
                    // 构造器中的this是谁,this是类的实例对象
                    this.name = name
                    this.age = age
                }
                // 一般方法, speak 被放在了原型对象上,供实例使用
                // 通过person实例调用speak时,speak中的this就是person实例   
                speak() {
                    console.log(`我叫${this.name},我的年龄是${this.age}`);
                }
            }
            // 创建一个person的实例对象
            const p1 = new Person('tom', 18);
            const p2 = new Person('jerry', 19);
            p1.speak();
            p2.speak();
            // 继承
            class Student extends Person {
                constructor(name, age, grade) {
                    super(name, age);
                    this.grade = grade;
                }
                speak() {
                    console.log(`我叫${this.name},我年龄是${this.age},我读的是${this.grade}年级`);
                }
                study() {
                    // 谁调用谁就是this
                    console.log("我很努力地学习");
                }
            }
            const s1 = new Student("付立翔", 21, "大二");
            s1.speak();
            s1.study();
    
        </script>
    

类式组件

    <script type="text/babel">
        // 1. 创建类式组件
        class MyComponent extends React.Component {
            // render 放在MyComponent的原型对象上
            render() {
                return <h2>我是类定义的组件(适用于[复杂组件的定义])</h2>
            }
        }
        // 2. 渲染组件
        ReactDOM.render(<MyComponent />, document.getElementById("test"));
        /**
         * 1. React解析组件发现了MyComponent 组件
         * 2. React 发现这是一个类组件,React new 出来一个MyComponent 组件实例,并通过组件实例来调用原型上的方法
         * 3. React 通过返回的虚拟DOM,转换成真实DOM ,随后呈现在页面上
         */
    </script>

state

    <script type="text/babel">
        // 1. 定义一个类组件
        class Weather extends React.Component {
            constructor(props) {
                super(props);
                // 初始化状态
                this.state = {
                    isHot: false,
                    wind:"微风"
                }
                // 让自定义的this指向Weather的实例对象
                this.demo = this.demo.bind(this);
            }

            // 读东西,做展示
            render() {
                // 读取状态
                const {isHot,wind} = this.state
                return <h1 onClick={ this.demo} >今天天气很{isHot ? '炎热' : '寒冷'}{wind}</h1>
            }

            // 点击状态发生改变
            demo() {
                // 严重注意:状态state 不可以直接更改,要借助一个内置的API去更改!!!内部的信息发生改变,是一种合并,不是替换
                // this.state.isHot = !this.state.isHot;
                console.log(this);
                const isHot = this.state.isHot;
                this.setState({isHot:!isHot})
                console.log('标题别点击了');
            }
        }
        // 2. 渲染组件到页面
        ReactDOM.render(<Weather />, document.getElementById("test"));


    </script>

state精简

    <script type="text/babel">
        // 1. 定义一个类组件
        class Weather extends React.Component {
          
            // 成员属性 , 初始化状态
            state = {
                isHot: false,
                wind: "微风"
            }
            render() {
                const { isHot, wind } = this.state
                return <h1 onClick={this.demo} >今天天气很{isHot ? '炎热' : '寒冷'}{wind}</h1>
            }

            /**
             * 注意箭头函数没有this,如果使用this会向上寻找this
             */
            demo = () => {
                console.log(this);
                const isHot = this.state.isHot;
                this.setState({ isHot: !isHot })
                console.log('标题别点击了');
            }


        }
        ReactDOM.render(<Weather />, document.getElementById("test"));


    </script>

props

<body>
    <!-- 准备好一个容器 -->
    <div id="test"></div>
    <div id="test1"></div>
    <div id="test2"></div>
    <!-- 加载 React。-->
    <!-- 注意: 部署时,将 "development.js" 替换为 "production.min.js"。-->
    <script type="text/javascript" src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <script type="text/javascript" src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
    <script type="text/javascript" src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
    <!-- 后面一定要使用babel -->
    <script type="text/babel">
        // 创建组件
        class Person extends React.Component {
            render() {
                const {name,sex,age} = this.props;
                return (
                    <ul>
                        <li>姓名:{name}</li>
                        <li>性别:{sex}</li>
                        <li>年龄:{age}</li>
                    </ul>
                )
            }

        }
        // 渲染组件到界面
        ReactDOM.render(<Person name="付立翔" sex="男" age="21" />, document.getElementById("test"));
        ReactDOM.render(<Person name="黄晓明" sex="男" age="49" />, document.getElementById("test1"));
        ReactDOM.render(<Person name="马云" sex="男" age="50" />, document.getElementById("test2"));
    </script>
</body>

外部传参

        // 创建组件
        class Person extends React.Component {
            render() {
                const {name,sex,age} = this.props;
                return (
                    <ul>
                        <li>姓名:{name}</li>
                        <li>性别:{sex}</li>
                        <li>年龄:{age}</li>
                    </ul>
                )
            }

        }
        // 渲染组件到界面
        const p = {name:"flx",sex:"nan",age:"21"}
        // ...p 这是展开运算符
        /**
         * 详解见下图
         */ 
        ReactDOM.render(<Person {...p}/>, document.getElementById("test"));
        ReactDOM.render(<Person name="黄晓明" sex="男" age="49" />, document.getElementById("test1"));
        ReactDOM.render(<Person name="马云" sex="男" age="50" />, document.getElementById("test2"));

image-20210525200133770

对类型进行限制

    <script type="text/babel">
        // 创建组件
        class Person extends React.Component {
            render() {
                const { name, sex, age } = this.props;
                return (
                    <ul>
                        <li>姓名:{name}</li>
                        <li>性别:{sex}</li>
                        <li>年龄:{age}</li>
                    </ul>
                )
            }
        }

        // 对传递的属性名进行限制
        Person.propTypes = {
            name: PropTypes.string.isRequired,
            sex: PropTypes.string,
            age: PropTypes.number,
            dd:  PropTypes.func,
        };
        // 如果没有传递一个属性值就设置一个默认值
        Person.defaultProps = {
            sex: "不男不女",
            age: 18
        }

        // 渲染组件到界面
        const p = { name: "1", age: 21 }
        // ...p 这是展开运算符
        ReactDOM.render(<Person {...p} dd={dd} />, document.getElementById("test"));
        ReactDOM.render(<Person name="黄晓明" sex="男" age={49} />, document.getElementById("test1"));
        ReactDOM.render(<Person name="马云" sex="男" age={50} />, document.getElementById("test2"));


        function dd() {
            console.log("我是一个大好人");
        }
    </script>

props简写方式

   <script type="text/babel">
        // 创建组件
        class Person extends React.Component {
            // 对传递的属性名进行限制
            static propTypes = {
                name: PropTypes.string.isRequired,
                sex: PropTypes.string,
                age: PropTypes.number,
                dd: PropTypes.func,
            };
            // 如果没有传递一个属性值就设置一个默认值
            static defaultProps = {
                sex: "不男不女",
                age: 18
            }
            render() {
                const { name, sex, age } = this.props;
                return (
                    <ul>
                        <li>姓名:{name}</li>
                        <li>性别:{sex}</li>
                        <li>年龄:{age}</li>
                    </ul>
                )
            }
        }



        // 渲染组件到界面
        const p = { name: "1", age: 21 }
        // ...p 这是展开运算符
        ReactDOM.render(<Person {...p} dd={dd} />, document.getElementById("test"));
        ReactDOM.render(<Person name="黄晓明" sex="男" age={49} />, document.getElementById("test1"));
        ReactDOM.render(<Person name="马云" sex="男" age={50} />, document.getElementById("test2"));


        function dd() {
            console.log("我是一个大好人");
        }
    </script>

函数式组件可以使用props

     function Person(props) {
            const { name, sex, age } = props;
            return (
                <ul>
                    <li>姓名:{name}</li>
                    <li>性别:{sex}</li>
                    <li>年龄:{age}</li>
                </ul>
            )
        }


        Person.propTypes = {
            name: PropTypes.string.isRequired,
            sex: PropTypes.string,
            age: PropTypes.number,
            dd: PropTypes.func,
        };
        // 如果没有传递一个属性值就设置一个默认值
        Person.defaultProps = {
            sex: "不男不女",
            age: 18
        }

        const p = { name: "1", age: 21 }
        // ...p 这是展开运算符
        ReactDOM.render(<Person {...p} dd={dd} />, document.getElementById("test"));
        ReactDOM.render(<Person name="黄晓明" sex="男" age={49} />, document.getElementById("test1"));
        ReactDOM.render(<Person name="马云" sex="男" age={50} />, document.getElementById("test2"));


        function dd() {
            console.log("我是一个大好人");
        }

ref

  1. 使用回调方式创建ref

            // 创建组件
            class Demo extends React.Component {
                showDate = () => {
                    const { input1 } = this;
                    alert(input1.value);
                }
                showDate2 = () => {
                    const { input2 } = this;
                    alert(input2.value);
                }
                render() {
                    return (
                        <div>
                            <input ref={c => this.input1 = c} type="text" placeholder="点击按钮提示数据"></input>
                            <button ref={c => this.btn = c} onClick={this.showDate}>点击我提示左侧的数据</button>
                            <input ref={c => this.input2 = c} onBlur={this.showDate2} type="text" placeholder="失去焦点提示数据"></input>
                        </div>
                    )
                }
            }
            // 渲染dom
            ReactDOM.render(<Demo />, document.getElementById("test"));
    
  2. 使用函数方式创建ref 注意版本问题,低版本没有这个函数 ,推荐使用这个,可以清晰的看见这个

            // 创建组件
            class Demo extends React.Component {
                myRef = React.createRef()
                myRef2 = React.createRef()
                showDate = () => {
                    alert(this.myRef.current.value);
                }
                showDate2 = () => {
                    alert(this.myRef2.current.value);
                }
                render() {
                    return (
                        <div>
                            <input ref={this.myRef} type="text" placeholder="点击按钮提示数据"></input>
                            <button ref={c => this.btn = c} onClick={this.showDate}>点击我提示左侧的数据</button>
                            <input ref={this.myRef2} onBlur={this.showDate2} type="text" placeholder="失去焦点提示数据"></input>
                        </div>
                    )
                }
            }
            // 渲染dom
            ReactDOM.render(<Demo />, document.getElementById("test"));
    

    注意:尽量避免使用ref,如果事件本身的发起者和数据是一个可以通过enent.taget拿到

受控组件

    <script type="text/babel">
        // 创建组件
        class Demo extends React.Component {
            handleSubmit = (event) => {
                event.preventDefault();// 阻止表单提交
            }
            saveFromDate = (dataType) => {
                return (event)=> {
                   this.setState({[dataType]:event.target.value})
                }
            }
            render() {
                return (
                    <form action="" onSubmit={this.handleSubmit}>
                        用户名:<input type="text" onChange={this.saveFromDate("username")} name="username" />
                        密码:<input type="password" onChange={this.saveFromDate("password")} name="password" />
                        <button type="submit">登录</button>
                    </form>
                )
            }
        }
        // 渲染组件
        ReactDOM.render(<Demo />, document.getElementById("test"));
    </script> 

非受控组件

    <script type="text/babel">
        // 创建组件
        class Demo extends React.Component {
            username = React.createRef()
            password = React.createRef()



            handleSubmit = (event) => {
                event.preventDefault();// 阻止表单提交
                console.log(this.username.current.value+this.password.current.value);
            }

            render() {
                return (
                    <form action="" onSubmit={this.handleSubmit}>
                        用户名:<input type="text" ref={this.username} name="username" />
                        密码:<input type="password" ref={this.password} name="password" />
                        <button type="submit">登录</button>
                    </form>
                )
            }
        }
        // 渲染组件
        ReactDOM.render(<Demo />, document.getElementById("test"));
    </script> 

生命周期

  <script type="text/babel">
        // 创建组件
        class Lift extends React.Component {

            state = { opacity: 1 }

            death = () => {

                // 卸载组件
                ReactDOM.unmountComponentAtNode(document.getElementById("test"));
            }

            // 组件挂载完毕
            componentDidMount = () => {
                this.timer = setInterval(() => {
                    let { opacity } = this.state;
                    opacity -= 0.05;
                    if (opacity <= 0) { opacity = 1; }
                    this.setState({ opacity });
                }, 100);
            }

            // 组件将要卸载
            componentWillUnmount = () => {
                // 清除定时器
                clearInterval(this.timer);
            }
			// 初始化渲染,状态更新之后
            render() {
	
                return (
                    <div>
                        <h2 style={{ opacity: this.state.opacity }}>学不会怎么办?</h2>
                        <button onClick={this.death}>不活了</button>
                    </div>
                )
            }
        }
        // 挂载到界面
        ReactDOM.render(<Lift />, document.getElementById("test"));
    </script>

组件的生命周期(旧)

  <script type="text/babel">
        class Demo extends React.Component {

			// 第一次调用 :构造函数
            constructor(props) {
				console.log("Count---constuctor");
				super(props)
				// 初始化状态
				this.state = {count:0}
            }
			
			// 第二次调用 :将要挂载的钩子
			componentWillMount = ()=>{
				console.log("Count---componentWillMount");
			}
			
			// 第三次调用 挂载完毕 : 挂载完毕的钩子
			componentDidMount = ()=>{
				console.log("Count---componentDidMount");
			}
			
			
			/*
			 * 更新的路线
			 */
			// 组件更新状态是否更新页面 || 默认返回true || 返回false不更新页面
			shouldComponentUpdate = ()=>{
				console.log("Count---shouldComponentUpdate");
				return true;
			}
			
			// 组件将要更新
			componentWillUpdate(){
				console.log("Count---ComponentWillUpdate");
			}
			
			// 组件更新完毕
			componentDidUpdate(){
				console.log("Count---componentDidUpdate");
			}
			
			// 卸载组件之前调用钩子
			componentWillUnmount= ()=>{
				console.log("Count---componentWillUnmount")
			}
			
			
			
			/*
			 * 强制更新
			 */
			force = ()=>{
				this.forceUpdate();
			}
			
			
			/*
			 * end: 最终卸载组件
			 */
			 
			// 加1按钮的回调
            add = () => {
                this.setState({ count: this.state.count + 1 })
            }
			
			// 卸载组件
			death = ()=>{
				ReactDOM.unmountComponentAtNode(document.getElementById("test"))
			}
			
			// 状态信息
            render() {
				console.log("Count---render");
                const { count } = this.state;
                return (
                    <div>
                        <h2>当前求和为{count}</h2>
                        <button onClick={this.add}>点我加一</button>
						<button onClick={this.death}>点击我卸载组件</button>
						<button onClick={this.force}>不更改状态,强制更新</button>
                    </div>
                )
            }
        }
		
        ReactDOM.render(<Demo />, document.getElementById("test"));
    </script>

image-20210528104350693

组件的生命周期全(旧)

    <script type="text/babel">
		// 创建组件
        class Demo extends React.Component {

			// 第一次调用 :构造函数
            constructor(props) {
				console.log("Count---constuctor");
				super(props)
				// 初始化状态
				this.state = {count:0}
            }
			
			// 第二次调用 :将要挂载的钩子
			componentWillMount = ()=>{
				console.log("Count---componentWillMount");
			}
			
			// 第三次调用 挂载完毕 : 挂载完毕的钩子
			componentDidMount = ()=>{
				console.log("Count---componentDidMount");
			}
			
			
			/*
			 * 更新的路线
			 */
			// 组件更新状态是否更新页面 || 默认返回true || 返回false不更新页面
			shouldComponentUpdate = ()=>{
				console.log("Count---shouldComponentUpdate");
				return true;
			}
			
			// 组件将要更新
			componentWillUpdate(){
				console.log("Count---ComponentWillUpdate");
			}
			
			// 组件更新完毕
			componentDidUpdate(){
				console.log("Count---componentDidUpdate");
			}
			
			// 卸载组件之前调用钩子
			componentWillUnmount= ()=>{
				console.log("Count---componentWillUnmount")
			}
			
			
			
			/*
			 * 强制更新
			 */
			force = ()=>{
				this.forceUpdate();
			}
			
			
			/*
			 * end: 最终卸载组件
			 */
			 
			// 加1按钮的回调
            add = () => {
                this.setState({ count: this.state.count + 1 })
            }
			
			// 卸载组件
			death = ()=>{
				ReactDOM.unmountComponentAtNode(document.getElementById("test"))
			}
			
			// 状态信息
            render() { 
				console.log("Count---render");
                const { count } = this.state;
                return (
                    <div>
                        <h2>当前求和为{count}</h2>
                        <button onClick={this.add}>点我加一</button>
						<button onClick={this.death}>点击我卸载组件</button>
						<button onClick={this.force}>不更改状态,强制更新</button>
                    </div>
                )
            }
        }
		
		// 定义父组件
		class A extends React.Component{
			// 初始化状态
			state = {carName:"奔驰"}
			
			changCar = ()=>{
				let {carName} = this.state;
				this.setState({carName : "奥拓"});
			}
			
			render(){
				return(
				<div>
					<div>我是A组件</div>
					<button onClick={this.changCar}>换车</button>
					<B carName={this.state.carName} />
				</div>
				)
			}
		}
		
		// 定义子组件
		class B extends React.Component{
			
			// 组件将要接收新的Props,默认接收props参数
			componentWillReceiveProps(props){
				console.log("B---componentWillReceiveProps",props)
			}
			
			shouldComponentUpdate(){
				console.log("B---shouldComponentUpdate")
				return true;
			}
			
			componentWillUpdate(){
				console.log("B---componentWillUpdate")
			}
			
			componentDidUpdate(){
				console.log("B---componentDidUpdate")
			}
			
			componentWillUnmount(){
				console.log("B---componentWillUnmount")
			}
			
			render(){
				return(
					<div>我是B组件,接收到的车是:{this.props.carName}</div>
				)
			}
		}
		
		
        ReactDOM.render(<A />, document.getElementById("test"));
    </script>

getSnapshotBefroeUpdate


## <font color='red'>重点: react和vue中的key有什么作用</font>

1. 虚拟DOM中的key的作用:

   <font color='orange'>1)**简单的说**:key是虚拟DOM对象的标识符,在更新显示时key起着极其重要的作用</font>

   <font color='yellow'>2) **详细的说**:当状态中的数据发生变化的时候,react会根据**<font color='red'>[新数据]</font>**生成**<font color='red'>[新的虚拟DOM]</font>**,随后React进行**<font color='red'>[新虚拟DOM]</font>**与**<font color='red'>[旧虚拟DOM]</font>**的diff比较,比较规则如下:</font>

2. 旧虚拟DOM中找到了与新虚拟DOM想相同的key:

   1).<font color='cornflowerblue'>若虚拟DOM中的内容</font><font color='green'>没变</font>,<font color='purple'>直接使用</font>之前的<font color='orange'>真实DOM</font>

   2)<font color='cornflowerblue'>.若虚拟DOM中的内容</font><font color='green'>变了</font>,<font color='purple'>则生成</font>新的<font color='yellow'>真实的DOM,最后替换掉页面中之前的真实DOM</font>

3. 旧虚拟DOM中未找到与新虚拟DOM相同的key根据数据创建新的真实DOM,随后渲染到页面

# R eact 脚手架

## 相关命令

npm i create-react-app -g

 1. yarn start -----------------------------------开启

 2. yarn build -----------------------------------最终进行一次打包

 3. yarn test ------------------------------------几乎不用,

 4. yarn eject ----------------------------------react 怕你碰坏webpack的配置文件,所以都隐藏了,一旦暴露出来,就隐藏不回去了

    ![image-20210528213406713](https://gitee.com/fu-lixiang/flx-img/raw/master/img/image-20210528213406713.png)

## nanoid

添加方式 yarn add nanoid

```react
import {nanoid} from "nanoid"
nanoid(); // 每次生成一个唯一值

使用axios请求

 getStudentData = () => {
        axios({
            method: 'get',
            url: 'http://localhost:3000/students'
        }).then(function (value) { console.log(value.data); }, function (reason) { console.log(reason); })
    }

如果请求的不是3000端口的服务器,配置中间服务器

// 在package.json中
加入一条 , 端口号5000根据服务器的端口改
"proxy": "http://localhost:5000"

配置多个代理服务器

// 在pulic中新建setupProxy.js
const proxy = require('http-proxy-middleware')

module.exports = function (app) {
    app.use(
        proxy('/api1', { // 遇见api1前缀的请求,就会触发该代理配置
            target: 'http://localhost:5000', // 转发的地址
            changeOrigin: true, // 控制服务器收到的请求头中Host字段的值 ,年轻人,偷,骗,我七十二岁的老服务器
            pathRewrite: { '^/api1': '' } // 重写请求路径
        }),
        proxy('/api2', {
            target: 'http://localhost:5001',
            changeOrigin: true,
            pathRewrite: { '^/api2': '' }
        })
    ) 
}

消息订阅与发布机制

使用

npm install pubsub-js --save

import PubSub from ‘pubsub-js’// 引入

const PubSub = require(“pubsub-js”); // comment.js 引入

消息发布

PubSub.publish('delete',data) // 发布消息

消息订阅

var token = PubSub.subscribe('delete',function(_,data){});// 订阅

取消订阅

PubSub.unsubscribe(token)

React 路由

1. 什么是路由

  1. 一个路由就是一个映射关系(key:value)

  2. key 是路径,value可能是function 或 component

路由的原理

根据浏览器的history

因为浏览器的结构是一个栈的结构

image-20210602075846802

BrowserRouter

 <div>
                <div className="row">
                    <div className="col-xs-offset-2 col-xs-8">
                        <div className="page-header"><h2>React Router Demo</h2></div>
                    </div>
                </div>
                <BrowserRouter> {/* 包在最外层 */}
                    <div className="row">
                        <div className="col-xs-2 col-xs-offset-2">
                            <div className="list-group">
                                {/* 原生HTML中,使用a标签来跳转到不同的页面 */}
                                {/* <a className="list-group-item active" href="./about.html">About</a>
                            <a className="list-group-item" href="./home.html">Home</a> */}

                                {/* 在React中靠路由连接实现切换组件 */}
                                    <Link className="list-group-item" to="/about">About</Link>
                                    <Link className="list-group-item" to="/home">Home</Link>

                            </div>
                        </div>
                        <div className="col-xs-6">
                            <div className="panel">
                                <div className="panel-body">
                                    {/* 注册路由 */}
                                    <Route path="/about" component={About} />
                                    <Route path="/home" component={Home} />
                                </div>
                            </div>
                        </div>
                    </div>
                </BrowserRouter>
            </div>

简化

import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter } from 'react-router-dom'
import App from './App'

ReactDOM.render(
    <BrowserRouter>
        <App />
    </BrowserRouter>
    , document.getElementById('root'))

切换 hash route

import React from 'react'
import ReactDOM from 'react-dom'
import { HashRouter } from 'react-router-dom'
import App from './App'

ReactDOM.render(
    <HashRouter>
        <App />
    </HashRouter>
    , document.getElementById('root'))

路由组件传递数据

路由组件与一般组件

  1. 写法不同

    一般组件:

    路由组件:

  2. 存放位置不同

    一般组件: components

    路由组件: pages

  3. 接收的props 不同

    一般组件: 写组件标签时传递了什么,就能收到什么

    路由组件:接收到三个固定的属性

    image-20210602104852001

NavLink

添加这个自动给链接加一个类action

设置样式 activeClassName

  <NavLink activeClassName="demo" className="list-group-item" to="/about">About</NavLink>
  <NavLink activeClassName="demo" className="list-group-item" to="/home">Home</NavLink>

NavLink 的封裝

import React, { Component } from 'react'
import { NavLink } from 'react-router-dom'

export default class MyNavLink extends Component {
    render() {
        return (
            <NavLink activeClassName="demo" className="list-group-item" {...this.props} ></NavLink>
        )
    }
}

{/* children 这个是在标签内的文字 */}
      <MyNavLink to="/home" children="dddd" title="dddd"></MyNavLink>

注册的路由特别多的解决办法

         <Switch>
                                <Route path="/about" component={About} />
                                <Route path="/home" component={Home} />
                                 <Route path="/home" component={About} />
		</Switch>
		一个以上包裹Switch
		只匹配一次,不会继续向下匹配
import { NavLink, Route,Switch } from 'react-router-dom'

多级路由样式丢失的问题

查找丢失的css,把.去掉

或者修改路由模式为hashRouter

路由的模糊匹配和精准匹配

/home/a/b

home a b

/home

image-20210602141330863

使用精准配置 需要 慎用 没有影响不要乱开 有可能导致无法匹配二级路由

redirect

      {/* 注册路由 */}
                               <Switch>
                                   <Route path="/about" component={About} />
                                   <Route path="/home" component={Home} />
                                   <Route path="/home" component={About} />
                                   {/* 兜底的 */}
                                   <Redirect to="/about" />
                               </Switch>

嵌套路由的使用

// 嵌套路由 在一个路由组件中又有一个路由组件
注意匹配模式
例如: /home/news 先匹配home,返回home,在匹配news,返回news,如果使用精准匹配就会出错

路由组件传递params参数

 render() {
        const { messageArr } = this.state;
        return (
            <div>
                <ul>
                    {
                        messageArr.map((msgObj) => {
                            return (
                                /* 向路由组件传递params参数 */
                                <li key={msgObj.id}><Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>&nbsp;&nbsp;</li>
                            )
                        })
                    }

                </ul>
                {/* 声明接收 */}
                <Route path="/home/message/detail/:id/:title" component={Detail} />

            </div>

        )
    }
// 拿
 const {id,title} = this.props.match.params;

路由组件传递search参数

     /* 向路由组件传递search参数 */
<Link to={`/home/message/detail?id=${msgObj.id}&title=${msgObj.title}`}></Link>
  {/* search参数无需声明接收 */}
<Route path="/home/message/detail" component={Detail} />
// 导入querystring
import qs from 'querystring'
// 将json转dd=dd&cc=cc
qs.stringfiy(obj) 
//反转
qs.parse(str)
// 接收
   const {search} = this.props.location
   const {id,title} = qs.parse(search.slice(1))

路由组件传递state参数

    {/* 向路由传递state参数 */}
<Link to={{ pathname: '/home/message/detail', state: { id: msgObj.id, title: msgObj.title } }}></Link>
 {/* state参数无需声明接收 */}
<Route path="/home/message/detail" component={Detail} />
  // 接收state参数
const {id,title} = this.props.location.state
// 注意:如果缓存和历史记录清除之后就会取不到值,因为state是保存在浏览器的history里
// 防止保错
  const {id,title} = this.props.location.state || {};

        const findResult = DetailData.find((detailObj) => {
            return detailObj.id === id
        }) || {};

push 和 replace 痕迹

replace 没有历史记录,只是替换

如果点击图片实现跳转

编程式路由导航

push 和 replace

&nbsp;<button onClick={()=>{this.pushShow(msgObj.id,msgObj.title)}}>push查看</button>
&nbsp;<button onClick={()=>{this.replaceShow(msgObj.id,msgObj.title)}}>replace查看</button>
    replaceShow = (id, title) => {
        // 使用params        
        // this.props.history.replace(`/home/message/detail/${id}/${title}`);

        // 使用search
        // this.props.history.replace(`/home/message/detail?id=${id}&title=${title}`);

        // 使用state
        this.props.history.replace(`/home/message/detail`, { id, title });
    }
    pushShow = (id, title) => {
        // 使用params        
        // this.props.history.push(`/home/message/detail/${id}/${title}`);

        // 使用search
        // this.props.history.push(`/home/message/detail?id=${id}&title=${title}`);

        // 使用state
        this.props.history.push(`/home/message/detail`, { id, title });
    }

前进后退

    back = () => {
        this.props.history.goBack();
    }
    forward = () => {
        console.log(this.props);
        this.props.history.goForward();
    }
    // go
    // 后退一步
    this.props.history.go(-1);
	// 后退两步
	this.props.history.go(-2);
	// 前进一步
	this.props.history.go(1);
	// 前进两步
	this.props.history.go(2);

自动跳转

    componentDidMount() {
        setTimeout(() =>{ this.props.history.push('/home/message') },2000);

在一般组件中使用路由组件api

import {withRouter} from 'react-router-dom'
export default withRouter(组件)

image-20210612170724130

BrowserRouter 和 HashRouter 的区别

image-20210612170934200

antd

安装

yarn add antd

修改默认样式

image-20210613082446492

image-20210613082622999

redux

能不用就不用,复杂的组件嵌套关系可以使用

redux原理图

yarn add redux
/* 
    该文件专门用于暴露一个store对象,整个应用只有一个store对象
*/
/src/redux/store.js

// 引入createStore, 专门用于创建redux中最为核心的store对象
import {createStore} from 'redux'
// 引入为Count组件服务的reducer
import countReducer from './count_reducer'

export default createStore(countReducer);
/* 
    1. 该文件是用于创建一个为Count组件服务的reducer,reducer的本质就是一个函数
    2. reducer函数会接到两个函数,分别为:之前的状态(preState),动作对象(action)
*/
/src/redux/count_reducers

const initState = 0;
export default function countReducer(previousState = initState, action) {
    const { type, data } = action;
    // 根据type决定如何加工数据
    switch (type) {
        case 'increment':
            previousState + data; // 如果是加
        case 'decrement':
            previousState - data; // 如果是减 

        default:
            return previousState;
    }
}

注意:每次更改数据不会引起重绘,加入一个store检测是否更新,如果更新就重绘

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import store from './redux/store'

ReactDOM.render(
  // 可以检查React组件有没有错误
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

// 检查有没有改变,如果有改变就重绘重排
store.subscribe(() => {
  ReactDOM.render(
    // 可以检查React组件有没有错误
    <React.StrictMode>
      <App />
    </React.StrictMode>,
    document.getElementById('root')
  );
});
  1. 理解:

    reducer本质是一个函数,接收previousState,action

    reducer初始化,加工

    reducer被第一次调用时,是store自动完成的

    ​ 传递的previousState是undefined

    ​ 传递的action:{type:@@REDUX/INIT_a.2.b.3}

    在index.js中监测store中状态的改变,一旦发送改变重新渲染

    备注:数据改变不刷新页面,需要自己写数据改变刷新页面

/* 
    该文件专门为Count组件生成action对象
*/
/src/redux/count_action.js
export const createIncrementAction = value => ({ type: "add", value })

export const createDecrementAction = value => ({ type: "decrement", value })


react 执行异步任务

不是必须要用的,看应用场景

yarn add redux-thunk

需要使用中间件来转换

/src/redux/store

+ import {createStore,applyMiddleware} from 'redux'
import Count from './count_reducers'
// 引入redux-thunk,用于支持thunk
import thunk from 'redux-thunk'

+ export default createStore(Count,applyMiddleware(thunk));
// 异步是一个函数
export const yibujiazai = (data, time) => {
    return () => { setTimeout(() => { store.dispatch(add(data)) }, time) }
}

react-redux

yarn add react-redux

react-redux模型图

image-20210615205608172

定义容器组件

/src/containers/Count/index.jsx
import CountUI from "../../components/Count"
// 引入connect 用于连接UI组件与redux
import {connect} from "react-redux"

// 连接ui组件
export default connect()(CountUI) 

// 通过父组件进行传递
import React, { Component } from 'react'
import Count from './containers/Count'
// 引入store
import store from "./redux/store"

export default class App extends Component {
    render() {
        return (
            <div>
                <Count store={store}></Count>
            </div>
        )
    }
}

通过connect连接ui组件

前面的括号是两个函数,后面是连接的ui组件名称

前面的函数传递的是状态,后面的函数传递的是操作状态的方法

connect (a,b)(UI) 
function a(state) {
    // state 是初始状态,store.getState()
    return { 
        n: {count:state}
    }
}

function b(dispatch) {
    // dispatch 是分发状态
    return {
        jia: number => dispatch(add(number)),
        jian:number=>dispatch(jian(number)),
        yubu:(number,time)=>dispatch(yibujiazai(number,time))
    }
}

// 精简模式 , dispatch自动分发
export default connect(state => ({ n: { count: state } }), ({
    jia: add,
    jian: jian,
    yubu: yibujiazai
}
)
)(CountUI)

注意:如果使用了react-redux,不用监测action是否改变,删除以下,connect()()自动render,优化点一

// 监测redux中的状态改变,若redux中的状态发送了改变,那么重新渲染组件
store.subscribe(()=>{
    ReactDOM.render(<App />, document.getElementById("root"));
})

provider

如果有以下这么多的组件,简化传递store

  {/* 给容器组件传递store */}  
​        <Count store={store}></Count>

​        <Demo1 store={store}></Demo1>

​        <Demo1 store={store}></Demo1>

​        <Demo1 store={store}></Demo1>

​        <Demo1 store={store}></Demo1>

​        <Demo1 store={store}></Demo1>

​        <Demo1 store={store}></Demo1>

​        <Demo1 store={store}></Demo1>

​        <Demo1 store={store}></Demo1>

​        <Demo1 store={store}></Demo1>

解决:

/src/index.js
import App from './App'
import React from 'react'
import ReactDOM from 'react-dom'
+ import store from './redux/store'
+ import { Provider } from 'react-redux'
ReactDOM.render(
 +   <Provider store={store}>
        <App />
 +  </Provider>
    , document.getElementById("root"));

优化containers 和 UI组件,将俩个文件合并为一个

/src/constainers/Count

import { connect } from "react-redux"
import { add, jian, yibujiazai } from "../../redux/count_action"
import React, { Component } from 'react'


class Count extends Component {
    increment = () => {
        console.log(this.props);
        this.props.jia(1);
    }
    jian = () => {
        this.props.jian(1);
    }
    jijia = () => { }
    yibu = () => {
        this.props.yubu(1, 500);
    }
    render() {
        return (
            <div>
                <div>{this.props.pre.count}</div>
                <button onClick={this.increment}>加一</button>
                <button onClick={this.jian}>减一</button>
                <button onClick={this.jijia}>奇数加一</button>
                <button onClick={this.yibu}>异步加载</button>
            </div>
        )
    }
}




export default connect(state => ({ pre: { count: state } }), ({
    jia: add,
    jian: jian,
    yubu: yibujiazai
}
)
)(Count)

优化总结

image-20210616202341482

数据共享

/redux/actions/count.js

/redux/reducers/count.js

store管理俩个组件

import { createStore, applyMiddleware, combineReducers } from 'redux'
import Count_reducer from './reducers/count'
import Person_reducer from './reducers/person'
import thunk from 'redux-thunk'


// 汇总所有的reducer变成一个reducer
const allReducer = combineReducers({
    he: Count_reducer,
    rens: Person_reducer
});

export default createStore(allReducer, applyMiddleware(thunk))

state变成总对象

export default connect(

state => ({yiduiren:state.rens})

)(Person);

改变连接的值

export default connect(
    state => ({ yiduiren: state.rens, data: state.he }),
    {
        jiaYiRen: AddPerson,
    }
    
    state 是redux全局保存的
    // 获取值
    this.props.yiduiren
    this.props.data

注意:多个组件reducers中type的值不能一样,不然会执行两次,

image-20210617103720493

image-20210617103736751

注意:对数组进行操作时,使用[…],使用数组unshift并不会render,因为redux对数组进行的是浅比较,比较的是地址值

redux调试工具

yarn add redux-devtools-extension
// 在store中
// 引入redux-devtools-extension
import { composeWithDevTools } from 'redux-devtools-extension'

// 汇总所有的reducer变成一个reducer
const allReducer = combineReducers({
    he: Count_reducer,
    rens: Person_reducer
});

export default createStore(allReducer, composeWithDevTools(applyMiddleware(thunk)))

image-20210617113841061

项目打包

npm i serve -g

yarn global add serve

serve build

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值