批里批里 (゜-゜)つ🍺 干杯~|

七つ一旋桜

园龄:4年2个月粉丝:6关注:3

2021-09-01 21:23阅读: 18评论: 0推荐: 0

React入门笔记

创建一个react单页面

npx create-react-app <项目名>
cd <项目名>
yarn start # 部署前端服务(使用yarn作为包管理器 )

执行yarn run build会在build目录中生成打包版本
其他命令:

  yarn start
    Starts the development server.

  yarn build
    Bundles the app into static files for production.

  yarn test
    Starts the test runner.

  yarn eject
    Removes this tool and copies build dependencies, configuration files
    and scripts into the app directory. If you do this, you can’t go back!

创建一个简单页面:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
    <script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
    <div id="app"></div>
    <script type="text/babel">
        ReactDOM.render(
            <h1>Hello, world</h1>,
            document.getElementById('app')
        );
    </script>
</body>
</html>

需要引入babel和react相关的包


实时渲染一个react对象

    <div id="root"></div>
    <script type="text/babel">
        function tick() {
            const element = (
                <div>
                    <h1>Hello, world</h1>
                    <h2>Is is {new Date().toLocaleTimeString()}.</h2>
                </div>
            );
            ReactDOM.render(
                element,
                document.getElementById('root')
            )
        }

        setInterval(tick, 1000);
    </script>

这个例子会在setInterval()回调函数,每秒都调用 ReactDOM.render()


组件

创建React的组件有两种方式

  1. 函数组件

    function foo(props) {
        return (
            <h1>hello, {props.name}</h1>
        )
    }
    
  2. 类组件

    class foo extends React.Component {
        render() {
            return (
                <h1>Hello, {this.props.name}</h1>
            )
        }
    }
    

以上两种定义方式定义的组件在React中是等效的

有一点需要注意:组件的名称必须开头大写


渲染组件

React组件吃了是DOM标签,也可以是自定义标签

const element = <div />; // html自带的div标签
const element = <Foo name="Sara" />; // 自定义的Foo标签

当 React 元素为用户自定义组件时,它会将 JSX 所接收的属性(attributes)以及子组件(children)转换为单个对象传递给组件,这个对象被称之为 “props”。

function Foo(props) {
    return <h1>Hello, {props.name}</h1>
}
ReactDOM.render(
    <Foo name="Sara"/>,
    document.getElementById('root')
)

网页效果:

image-20210901225815755


拆分组件

对于一个组件,可以通过带className属性的子标签作为该组件的子组件来把当前组件拆分

function SuperComponent(props) {
    return (
        <div className="SuperComponent">
            <img className="ChildComponent"
                src={props.info.imgUrl}
                alt={props.info.name}
                />
        </div>
    );
}
const info = {
    "imgUrl": "https://img1.gtimg.com/gamezone/pics/hv1/23/149/2287/148750193.jpg",
    "name": "img",
}
function Foo(props) {
    return (
        <div className="Foo">
            <Hello />
            <SuperComponent info={info}/> // 给props中的info赋值
        </div>
    )
}

提取子组件

当一个组件有着过多的子组件就会产生复杂的嵌套关系,这个时候为了复用子组件就需要提取子组件

function ChildComponent(props) {
    return (
        <img className="ChildComponent"
            src={props.info.imgUrl}
            alt={props.info.name} 
            />
    )
}

此时父组件中的子组件相关代码就可以用子组件标签代替了

function SuperComponent(props) {
    return (
        <div className="SuperComponent">
            <ChildComponent info={props.info}/>
        </div>
    );
}

定义函数式组件时要注意:所有React组件都要像纯函数一样保护他们的props不被更改

提取子组件到文件

当一个子组件过多时,可以将组件单独写到一个js文件里

Clock.js

import React from 'react'; // 导入React相关的包

class Clock extends React.Component {
	constructor(props) {
		super(props);
		this.state = {date: new Date()};
	}

	componentDidMount() {
		this.timerIO = setInterval(
			() => {
				this.setState({
					date: new Date()
				});
			},
			1000
		);
	}

	componentWillUnmount() {
		clearInterval(this.timerIO);
	}

	render() {
		return (
			<div>
				<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
			</div>
		)
	}	
}

export default Clock;

这样就能在index.js中引用

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import Clock from './Clock'
import reportWebVitals from './reportWebVitals';

ReactDOM.render(
  <React.StrictMode>
    <Clock />
  </React.StrictMode>,
  document.getElementById('root')
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals(console.log);

State & 生命周期

在最开始定义了一个动态显示时间的组件,现在将其封装

function Clock(props) {
    return (
        <div>
            <h1>hello world</h1>
            <h2>It is {props.date.toLocaleTimeString()}.</h2>
        </div>
    )
}

但是这个组件的date并不会实时更新,想要做到实时更新组件,就可以用state

state与props类似,但是state是组件私有的,并且完全受控于当前组件

使用state需要将函数式组件转换为类式组件

class Clock extends React.Component {
    render() {
        return (
            <div>
                <h1>Hello, world</h1>
                <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
            </div>
        )
    }
}

并且将this.props.date改为this.state.date

之后添加构造函数为this.state赋初始值

constructor(props) {
    super(props);
    this.state = {date: new Date()};
}

之后<Clock />也不再需要date属性了,它已经定义在state里了

但是此时组件依然不会自动更新,因此需要为其第一次被渲染时设置一个计时器。这在React中被称为挂载(mount),而当DOM中的组件被清除时还需要清除计时器,这被称为卸载(unmount)

在组件中可以声明一些函数来执行挂载和卸载

componentDidMount() {
}

componentWillUnmount() {
}

这些方法被称为生命周期方法

以下代码即可动态渲染<Clock />组件

tick() {
    this.setState({ // 使用setState方法更新state属性
        date: new Date() // 动态更新state的date
    });
}

componentDidMount() { // 挂载函数执行setInterval方法
    this.timerID = setInterval(
        () => this.tick(),
        1000
    );
}

componentWillUnmount() { // 卸载函数执行clearInterval方法来清除
    clearInterval(this.timerID)
}

使用State的注意事项

不要直接修改State

要注意一点:不要直接修改组件的state,而是使用setState()方法修改值

// Wrong
this.state.comment = 'Hello';

// Correct
this.setState({comment: 'Hello'});

State的更新可能是异步的

React可能会把多个setState()合并到一起调用,因此this.propsthis.state的更新可能是异步的,放在一起使用很容易产生bug

例如:

this.setState({
  counter: this.state.counter + this.props.increment,
});

这个例子中就将state和props放在一起使用了,这样的写法是错误

要解决这个问题可以让setState()接收一个函数而不是一个对象

this.setState((state, props) => ({
  counter: state.counter + props.increment
}));

数据向下流动

因为state是组件私有的,因此一个组件的父组件或者子组件是无法知道一个组件有没有state的

组件可以吧state作为props向下传递到他的子组件中,但是这样子组件本身无法知道一个state是来自父组件还是来自本身的


事件处理

  • React的事件采用小驼峰命名规则
  • 使用JSX语法时需要传入一个函数作为事件处理函数而非传入一个字符串

传统html:

<button onclick="activateLasers()">
    Activate Lasers
</button>

jsx:

<button onClick={activateLasers}>
    Activate Lasers
</button>

React还有一个不同点:不能通过返回false阻止默认行为,而要通过显示使用preventDefault

例如:

html

<a href="#" onclick="console.log('The link was clicked.'); return false">
  Click me
</a>

jsx

function ActionLink() {
  function handleClick(e) {
    e.preventDefault();
    console.log('The link was clicked.');
  }

  return (
    <a href="#" onClick={handleClick}>
      Click me
    </a>
  );
}

在这里,e 是一个合成事件。React 根据 W3C 规范来定义这些合成事件,所以你不需要担心跨浏览器的兼容性问题。如果想了解更多,请查看 SyntheticEvent 参考指南。

一个按钮组件

class Toggle extends React.Component {
    constructor(props) {
        super(props);
        this.state = {isToggleOn: true};
        // 为了在回调中使用 `this`, 这个bind必不可少
        this.handleClick = this.handleClick.bind(this);
    }

    handleClick() {
        this.setState(state => ({
            isToggleOn: !state.isToggleOn
        }));
    }

    render() {
        return (
            <button onClick={this.handleClick}>
                {this.state.isToggleOn ? 'ON' : 'OFF'}
            </button>
        )
    }
}

js语法中class方法不会默认绑定this,如果忘了绑定的话,调用函数时调用的值为undefine

如果觉得使用 bind 很麻烦,这里有两种方式可以解决。如果你正在使用实验性的 public class fields 语法,你可以使用 class fields 正确的绑定回调函数:

class LoggingButton extends React.Component {
  // 此语法确保 `handleClick` 内的 `this` 已被绑定。
  // 注意: 这是 *实验性* 语法。
  handleClick = () => {
    console.log('this is:', this);
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        Click me
      </button>
    );
  }
}

如果你没有使用 class fields 语法,你可以在回调中使用箭头函数

class LoggingButton extends React.Component {
  handleClick() {
    console.log('this is:', this);
  }

  render() {
    // 此语法确保 `handleClick` 内的 `this` 已被绑定。
    return (
      <button onClick={() => this.handleClick()}>
        Click me
      </button>
    );
  }
}

建议使用class fields,使用lambda可能会进行额外的渲染影响性能

向事件处理传递参数

有两种方式向处理函数传递参数

<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>

上述两种方式是等价的,分别通过箭头函数Function.prototype.bind 来实现。

在这两种情况下,React 的事件对象 e 会被作为第二个参数传递。如果通过箭头函数的方式,事件对象必须显式的进行传递,而通过 bind 的方式,事件对象以及更多的参数将会被隐式的进行传递。


条件渲染

根据条件分支进行不同的渲染

下面是一个例子,会根据props中isLoggedIn的值进行不同的渲染

function Greeting(props) {
    const isLoggedIn = props.isLoggedIn;
    if (isLoggedIn) {
        return (
            <h1>Welcome back!</h1>
        );
    } else {
        return (
            <h1>Please sign up.</h1>
        );
    }
}

ReactDOM.render(
  // Try changing to isLoggedIn={true}:
  <Greeting isLoggedIn={false} />,
  document.getElementById('root')
);

当然这样isLoggedIn的值就固定了,现在使用一个组件来使得isLoggedIn的值可以通过点击按钮改变

function LoginButton(props) {
    return (
        <button onClick={props.onClick}>
            Login
        </button>
    );
}

function LogoutButton(props) {
    return (
        <button onClick={props.onClick}>
            Logout
        </button>
    );
}

class LoginControl extends React.Component {
    constructor(props) {
        super(props);
        this.handleLoginClick = this.handleLoginClick.bind(this);
        this.handleLogoutClick = this.handleLogoutClick.bind(this);
        this.state = {isLoggedIn: false};
    }

    handleLoginClick() {
        this.setState({isLoggedIn: true});
    }

    handleLogoutClick() {
        this.setState({isLoggedIn: false});
    }

    render() {
        const isLoggedIn = this.state.isLoggedIn;
        let button;
        if (isLoggedIn) {
            button = <LogoutButton onClick={this.handleLogoutClick}/>
        } else {
            button = <LoginButton onClick={this.handleLoginClick}/>
        }

        return (
            <div>
                <Greeting isLoggedIn={isLoggedIn} />
                {button}
            </div>
        )
    }
}

ReactDOM.render(
    <LoginControl />,
    document.getElementById('app')
);

短路表达式

function Mailbox(props) {
  const unreadMessages = props.unreadMessages;
  return (
    <div>
      <h1>Hello!</h1>
      {unreadMessages.length > 0 &&
        <h2>
          You have {unreadMessages.length} unread messages.
        </h2>
      }
    </div>
  );
}

const messages = ['React', 'Re: React', 'Re:Re: React'];
ReactDOM.render(
  <Mailbox unreadMessages={messages} />,
  document.getElementById('root')
);

通过花括号包裹代码,你可以在 JSX 中嵌入任何表达式。这也包括 JavaScript 中的逻辑与 (&&) 运算符。它可以很方便地进行元素的条件渲染。

之所以能这样做,是因为在 JavaScript 中,true && expression 总是会返回 expression, 而 false && expression 总是会返回 false

因此,如果条件是 true&& 右侧的元素就会被渲染,如果是 false,React 会忽略并跳过它。

三目运算符

除了短路表达式,jsx也支持三目运算符condition ? true : false

render() {
  const isLoggedIn = this.state.isLoggedIn;
  return (
    <div>
      {isLoggedIn
        ? <LogoutButton onClick={this.handleLogoutClick} />
        : <LoginButton onClick={this.handleLoginClick} />
      }
    </div>
  );
}

需要注意的是三目运算符的滥用会导致代码可读性变低,如果条件很复杂,建议不使用三目运算符

阻止渲染

如果需要隐藏组件,那么会用到阻止渲染使render方法返回null

function WarningBanner(props) {
  if (!props.warn) {
    return null;
  }

  return (
    <div className="warning">
      Warning!
    </div>
  );
}

组件的render方法返回null并不会影响组件的生命周期

渲染列表

React渲染列表是用的是js内置的map()方法

const list = [1, 2, 3, 4, 5];
const listItems = list.map((item) =>
  <li>{item}</li>
);
ReactDOM.render(
  <ul>{listItems}</ul>,
  document.getElementById('root')
)

通常会在一个组件中渲染列表

function List(props) {
    const items = props.items;
    const listItems = items.map((item) => 
                                <li>{item}</li>
                               );
                                return (
                                <ul>{listItems}</ul>
                               );
}
const numbers = [1, 2, 3, 4, 5];
function App(props) {
    return (
        <div>
            <List items={numbers}/>
        </div>
    );
}

需要注意的是渲染列表时需要制定一个key来作为列表下标

const listItems = items.map((item) => 
                            <li key={number.toString()}>{item}</li>
                           );

key 帮助 React 识别哪些元素改变了,比如被添加或删除。因此你应当给数组中的每一个元素赋予一个确定的标识。

一个元素的 key 最好是这个元素在列表中拥有的一个独一无二的字符串。通常,我们使用数据中的 id 来作为元素的 key

key设置的位置:通常在map()函数中需要给标签设置key

key 会传递信息给 React ,但不会传递给你的组件。如果你的组件中需要使用 key 属性的值,请用其他属性名显式传递这个值:

const content = posts.map((post) =>
  <Post
    key={post.id}    
    id={post.id}    
    title={post.title} />
);

上面例子中,Post 组件可以读出 props.id,但是不能读出 props.key

jsx允许在打括号中嵌入任何表达式,因此可以内联使用map()函数

function List(props) {
    const items = props.items;
    return (
        <ul>
            {items.map((item) => 
                       <li key={item.toString()}>{item}</li>
                      )}
        </ul>
    );
}

路由

安装

yarn add react-router-dom

使用

  1. 导入核心组件

  2. 导入RouterRouteLink

    import { BrowserRouter as Router, Route, Link } from 'react-router-dom';
    
  3. 使用Router包裹整个应用并设置路由入口

    function App() {
      return (
        <Router>
          <div className="App">
            <div>React路由基础</div>
            {/*指定路由入口*/}
            <Link to="/first">页面一</Link>
          </div>
        </Router> 
      );
    }
    
  4. 使用Route组件配置路由规则和要展示的组件

    function App() {
      return (
        <Router>
          <div className="App">
            <div>React路由基础</div>
            {/*指定路由入口*/}
            <Link to="/first">页面一</Link>
              {/*指定路由出口*/}
            <Route path="/first" component={First}></Route>
          </div>
        </Router> 
      );
    }
    

    新建一个组件

    image-20211016144335943

    //First.js
    function First() {
    	return (
    		<div>
    			<p>页面一的内容</p>
    		</div>
    	)
    }
    
    export default First;
    

    然后在主页组件中导入

    // App.js
    import First from './component/First' // 导入组件
    
    function App() {
      return (
        <Router>
          <div className="App">
            <div>React路由基础</div>
            {/*指定路由入口*/}
            <Link to="/first">页面一</Link>
            <Route path="/first" component={First}></Route>
          </div>
        </Router> 
      );
    }
    
  5. 运行项目

    点击页面一,下面显示出了页面一的内容

    image-20211016144744257

基本使用

常用的Router组件有两种,分别是HashRouterBrowserRouter,后者更常用

  • HashRouter:使用url的哈希值实现(localhost/#/pathname)
  • BrowserRouter:使用H5的history API实现(lcoalhost/pathname)

执行过程

  1. 点击<Link>组件之后浏览器地址栏被修改
  2. React路由监听地址栏url的变化
  3. React路由内部便利所有Router组件,使用路由规则(path)与pathname进行匹配
  4. 匹配成功就展示对应组件内容

编程式导航

通过js代码实现页面跳转

使用 this.props.history.push('/pathname')就能实现路由的跳转

修改First.js的代码

// First.js
function First(props) {
	
	const handleLogin = () => {
		props.history.push('/home')
	}


	return (
		<div>
			<p>页面一的内容</p>
			<button onClick={handleLogin}>去Home</button>
		</div>
	)
	
}

export default First;

然后创建新的页面组件

image-20211016151436264

// Home.js
function Home() {
	return (
		<div>
			<h2>我是后台首页</h2>
		</div>
	)
}

export default Home;

并配置其路由

// App.js
import First from './component/First'
import Home from './component/Home'

function App() {
  return (
    <Router>
      <div className="App">
        <div>React路由基础</div>
        {/*指定路由入口*/}
        <Link to="/first">页面一</Link>
        <Route path="/first" component={First}></Route> 
        <Route path="/home" component={Home}></Route>
      </div>
    </Router> 
  );
}

这样点击去Home按钮时就会跳转到Home页面

image-20211016151553594

image-20211016151625674

返回上一个页面可以使用props.history.go(-1)

默认路由

默认路由:进入页面时就会匹配的路由

<Route path="/" component={Home}></Route>

path为"/"的路由就是默认路由

静态路由

安装

yarn add react-router-config
  1. 创建静态路由组件(类似于vue的<router-view>

    image-20211016161344370

    import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
    
    import First from '../component/First'
    import Home from '../component/Home'
    
    
    const routes = [
        {
            path: '/',
            component: Home,
            exact: true
        },
        {
            path: '/first',
            component: First,
            exact: true
        }
    ]
    
    export default function RouteView() {
        return (
            <div>
                <Router>
                    <RootRoute routeConfig={routes}/>
                </Router>
            </div>
        )
    }
    
    function RootRoute({routeConfig, basePath=''}) {
        if (routeConfig.length === 0) {
            return <></>
        }
        const rt = routeConfig.map((it, idx) => {
            const {Comp, Child, path, ...rest} = it;
            const newPath = (basePath + path).replace(/\/\./g, '/')
            const isExact= 'exact' in it ? it.exact : true
            return (
                <Route
                    key={newPath}
                    {...rest}
                    exact={isExact}
                    path={newPath}
                    render={(value) => {
                        let crt = <></>
                            if (Array.isArray(Child) && Child.length > 0) {
                                crt = RootRoute({Child, newPath})
                            }
                        return (
                            <Comp {...value}>
                                {crt}
                            </Comp>
                        )
                    }}
                    />
            )
        })
        return (
            <Switch>
                {rt}
            </Switch>
        )
    }
    
  2. 在App.js中进行引入

    import './App.css';
    
    import RouteView from './router/index'
    
    function App() {
      return (
          <div className="App">
            <div>React路由基础</div>
            <RouteView/>
          </div>
      );
    }
    
    export default App;
    

useReducer

基础用法

import { useReducer } from 'react';

function reducer(state, action) {
  // ...
}

function MyComponent() {
  const [state, dispatch] = useReducer(reducer, { age: 42 });
  // ...
}

例如

function reducer(state, action) {
  if (action.type === 'incremented_age') {
    return {
      age: state.age + 1
    };
  }
  throw Error('Unknown action.');
}

export default function Counter() {
  const [state, dispatch] = useReducer(reducer, { age: 42 });

  return (
    <>
      <button onClick={() => {
        dispatch({ type: 'incremented_age' })
      }}>
        Increment age
      </button>
      <p>Hello! You are {state.age}.</p>
    </>
  );
}

其中reducer函数也可以用switch来优化

function reducer(state, action) {
  switch (action.type) {
    case 'incremented_age': {
      return {
        name: state.name,
        age: state.age + 1
      };
    }
    case 'changed_name': {
      return {
        name: action.nextName,
        age: state.age
      };
    }
  }
  throw Error('Unknown action: ' + action.type);
}

本文作者:七つ一旋桜

本文链接:https://www.cnblogs.com/poifa/p/15216481.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   七つ一旋桜  阅读(18)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起