字节前端青训营笔记--React实战

645 阅读6分钟

React

类组件,函数组件

// 类组件
class ArticleList extends React.Component {
  render() {//渲染React元素
    const { articles } = this.props;//props由父组件传入,所有 React 组件都必须像纯函数一样保护它们的 props 不被更改
    return articles.map((item) => {
      const { title } = item;
      return <div> {title}</div>;
    });
  }
}
//函数组件
const ArticleList = (props) => {
  const { articles } = props;
  return articles.map((item) => {
    const { title } = item;
    return <div> {title}</div>;//注意渲染列表时要设置key属性
  });
const articles = [
  { id: 1, title: 'Article 1' },
  { id: 2, title: 'Article 2' },
  { id: 3, title: 'Article 3' },
];
function App() {
  return <ArticleList/*组件*/ articles/*属性,和子组件接受props的变量名一致*/={articles/*变量*/} />;
}

1.index.js调用render()函数;

2.React调用App组件;

3.App组件将变量articles作为props传入ArticleList组件;

4.ArticleList组件将div结构作为返回值;

5.React DOM更新页面

组件内部可变状态state

export default class Clock extends Component {
  constructor(props) {
    super(props);
    this.state = { date: new Date() };//构造函数,为state赋初值
  }

  componentDidMount() {//在组件已经被渲染到 DOM 中后运行
    this.timerID = setInterval(() => this.tick(), 1000);//定时器,每秒更新UI//挂载
  }

  componentWillUnmount() {
    clearInterval(this.timerID);//卸载
  }

  tick() {
    this.setState({//更新组件state
      date: new Date(),
    });
  }

  render() {
    return (
      <div>
        <h1>Current Time: {this.state.date.toLocaleTimeString()}.</h1>
      </div>
    );
  }
}

1.调用Clock的构造函数,初始化this.state

2.调用组件的render方法,更新DOM

3.调用componentDidMount()钩子函数,每秒调用一次tick()方法

4.tick()方法中调用setState(),重新render(),这时this.state.date已经改变

5.组件被移除时,调用componentWillUnmount(),计时器停止

受控组件

渲染表单的 React 组件还控制着用户输入过程中表单发生的操作。

  constructor(props){
    super(props);
    this.state = {
      title: '',
      content: ''
    }
  }
  handleChange = (e) => {//输入
    if (e.target.name === 'title') {
      this.setState({
        title: e.target.value
      })

    } else if (e.target.name === 'content') {
      this.setState({
        content: e.target.value
      })
    }
  };
  handleSave = async () => {//保存
    const {title, content} = this.state;
    if (title.length > 0 && content.length > 0) {
      console.log(title, content)
    }
  };
  render() {
    const {title, content} = this.props;
    return (
      <section>
        <h2>Add a New Post</h2>
        <form>
          <label htmlFor="title">Post Title:</label>
          <input
            type="text"
            id="title"
            name="title"
            value={title}
            onChange={this.handleChange}
          />
          <label htmlFor="content">Content:</label>
          <textarea
            id="content"
            name="content"
            value={content}
            onChange={this.handleChange}
          />
          <button type="button" className="button" onClick={this.handleSave}>
            Save Post
          </button>
        </form>
      </section>
    );
  }

非受控组件

表单数据将交由 DOM 节点来处理,组件通过使用ref来获取表单数据

class AddPostForm extends React.Component{

  constructor(props){
    super(props);
    this.titleInput = React.createRef();//创建ref
    this.contentInput = React.createRef();
  }

  handleSave = async () => {
    const title = this.titleInput.current.value;
    const content = this.contentInput.current.value;
    if (title.length > 0 && content.length > 0) {
      console.log(title, content)
    }
  };

  render() {
    return (
      <section>
        <h2>Add a New Post</h2>
        <form>
          <label htmlFor="title">Post Title:</label>
          <input
            type="text"
            id="title"
            name="title"
            ref={this.titleInput}//构造函数中定义的ref
          />
          <label htmlFor="content">Content:</label>
          <textarea
            id="content"
            name="content"
            ref={this.contentInput}
          />
          <button type="button" className="button" onClick={this.handleSave}>
            Save Post
          </button>
        </form>
      </section>
    );
  }
};

组件的组合用法

组件可以在其输出中引用其他组件。

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;//name是由使用这个组件的时候再传入的
}

function App() {
  return (
    <div>
      <Welcome name="Sara" />//传入name(字符串),动态传入的也可以组件,这样子实现了组件的组合使用
      <Welcome name="Cahal" />
      <Welcome name="Edite" />
    </div>
  );
}

State Hook

Hook 是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数。新的管理状态逻辑的方式。

function AddPostForm(props){
  const [title, setTitle] = useState('');//`useState` 会返回一对值:当前状态和一个让你更新它的函数,你可以在事件处理函数中或其他一些地方调用这个函数
  const [content, setContent] = useState('');
  const handleChange = (e) => {
    if (e.target.name === 'title') {
      setTitle(e.target.value)

    } else if (e.target.name === 'content') {
      setContent(e.target.value)
    }
  };
  const handleSave = async () => {
    if (title.length > 0 && content.length > 0) {
      console.log(title, content)
    }
  };
  return (
      <section>
        <h2>Add a New Post</h2>
        <form>
          <label htmlFor="title">Post Title:</label>
          <input
            type="text"
            id="title"
            name="title"
            value={title}
            onChange={handleChange}
          />
          <label htmlFor="content">Content:</label>
          <textarea
            id="content"
            name="content"
            value={content}
            onChange={handleChange}
          />
          <button type="button" className="button" onClick={handleSave}>
            Save Post
          </button>
        </form>
      </section>
    );
};

Effect Hook

 const [articles, setArticles] = useState([])

  useEffect(()=> {//每次组件在渲染的时候都会执行,但是内部的副作用函数不一定
    const fetchArticles = async () => {//带有副作用逻辑的函数
      try{
        const response = await client.get('/fakeApi/posts');//通过接口动态获取文章标题
        console.log(response)
        setArticles(response.posts);
      }catch(err){
        console.error(err)
      }
    }

    fetchArticles();
  }, []/*只有数组内的值发生变化的时候函数fetchArticles才会被执行。如果是空数组,则在组件被挂载的时候函数执行一次 */)

自定义 Hooks

将组件逻辑提取到可重用的函数中

function useCurrentDate(){//自定义hook函数,以use开头,封装date状态管理
  const [date, setCurrentDate] = useState(new Date());
  const tick = () => {
    setCurrentDate(new Date())
  }
  useEffect(() => {//副作用逻辑
    const timer = setInterval(()=>{tick()}, 1000)
    return () => {
      clearInterval(timer)
    }
  }, [])
  return date;
}
export default function Clock() {
  const date = useCurrentDate();
  return (
    <div>
      <h1>Current Time: {date.toLocaleTimeString()}.</h1>
    </div>
  );
}

只能在函数组件,自定义hook中调用hook

只能在函数的最顶层控制流中使用hook

React Router

 <Router>
      <Navbar />
      <div className="App">
        <Switch>
          <Route exact path="/"> //当路由匹配时,渲染对应的组件。Route定义了一个路由规则             
            <Home />
          </Route>
          <Route path="/editor">
            <Editor />
          </Route>
          <Route path="/login">
            <Login />
          </Route>
          <Route path="/posts/:id"//id是可变的参数通过history.push传递
            <Post />
          </Route>
        </Switch>
      </div>
    </Router>

<Link to="/">Home</Link>组件link实现路由跳转

也可以通过命令式的API调用方式:history.push

useHistory:获取history对象

useParams:获取路由中的参数

Redux

Redux基础

Store:存取全局状态,通过configureStore创建store对象。store.getState()拿到store保存的状态

Action:描述状态发生的变化。就是一个普通的js对象。必须包含type属性,来描述状态变化。 payload属性代表action包含的参数。

Dispatch:发送一个action,接受一个action作为参数。

Reducer:一个函数,根据当前应用state,和接受的action,计算新的state。必须是纯函数(不能有副作用,不能直接修改state,集成immer库来达到效果)

slice:一个slice模块包含了管理一个子状态所需的所有逻辑(state,actions,reducer等) 通过createSlice创建。通过useSelector函数获取state。通过useDispatch修改state

Postslice添加文章的slice

import { createSlice } from '@reduxjs/toolkit';
const initialState = [
  { id: '1', title: 'First Post!', content: 'Hello!' },
  { id: '2', title: 'Second Post', content: 'More text' },
];
const postsSlice = createSlice({//创建一个slice
  name: 'posts',
  initialState,//slice的初始状态
  reducers: {//用于添加文章
    addPost(state, action) {
      state.push(action.payload);
    },
  },
});
export const { addPost } = postsSlice.actions;
export default postsSlice.reducer;

全局Store

import { configureStore } from '@reduxjs/toolkit';
import posts from './postsSlice'//导入postSlice中的reducer函数
export default configureStore({//创建store对象
  reducer: {
    posts
  },
});

Home组件的redux入口

import React from 'react';
import { useSelector } from 'react-redux';
import PostListComp from './components/PostList';//一个普通的react组件
const Home = () => {
  const posts = useSelector((state) => state.posts)//获取全局的state中的posts即要添加的文章
  return (//获取到的posts传递给PostListComp组件
    <section>
      <PostListComp posts={posts} />     
    </section>
  );
};
export default Home;

editor组件的redux入口

import React from 'react';
import { useDispatch } from 'react-redux';
import { nanoid } from '@reduxjs/toolkit';
import { useHistory } from 'react-router-dom';
import AddPostForm from './components/AddPostForm';
import { addPost } from '../../states/postsSlice';
const AddPost = () => {
  const dispatch = useDispatch();//修改state
  const history = useHistory();
  const handleSubmit = (data) => {//提交事件的处理函数
    dispatch(
      addPost({
        ...data,
        id: nanoid(),
      }),
    );
    history.push('/');
  };
  return <AddPostForm onSubmit={handleSubmit} />;
};
export default AddPost;

Redux异步

thunk:延迟执行的一段代码

createAsyncThunk:创建异步执行的api

const initialState = {
  posts: [],//文章列表
  status: 'idle',//请求的状态
  error: null,
};

export const fetchPosts = createAsyncThunk('posts/fetchPosts', async () => {//(action的type,异步处理函数)
  const response = await client.get('/fakeApi/posts');//通过接口获取文章列表
  return response.posts;
});

export const addPost = createAsyncThunk(
  'posts/addPost',
  async (initialPost) => {
    const response = await client.post('/fakeApi/posts', { post: initialPost });
    return response.post;
  },
);

const postsSlice = createSlice({
  name: 'posts',
  initialState,
  reducers: {
    // addPost(state, action) {
    //   state.posts.push(action.payload);
    // },
  },
  extraReducers: {//因为fetchPosts是在postsSlice外部定义的,所以使用extraReducers而不是reducers
    [fetchPosts.pending]: (state, action) => {//有一个fetchPosts请求开始发送了
      state.status = 'loading';
    },
    [fetchPosts.fulfilled]: (state, action) => {//请求成功了
      state.status = 'succeeded';
      state.posts = action.payload;
    },
    [fetchPosts.rejected]: (state, action) => {//请求失败了
      state.status = 'failed';
      state.error = action.error.message;
    },
    [addPost.fulfilled]: (state, action) => {
      state.status = 'idle'
    },
  },
});

并不是所有的状态都需要维护在redux中,比如错误信息,loading等等都是在组件层面进行关注的,redux只有一个异步action

const AddPost2 = () => {
  const dispatch = useDispatch();
  const history = useHistory();
  const [addRequestStatus, setAddRequestStatus] = useState('idle');//组件内单独创建了一个状态,用来做loading
  const [error, setError] = useState(null);

  const handleSubmit = async (data) => {
    try {
      setAddRequestStatus('pending');
      const resultAction = await dispatch(
        addPost({
          ...data,
          id: nanoid(),
        }),
      );
      unwrapResult(resultAction);//对resultAction进行拆箱操作,看是否成功
      setAddRequestStatus('idle');
      history.push('/');//跳转回首页
    } catch (err) {
      setError(err.message);
      setAddRequestStatus('idle');
    }
  };


  return (
    <section>
      {addRequestStatus === 'pending' && <div className="loader">Loading...</div>}
      {error && <div className="error">{error}</div>}
      <AddPostForm onSubmit={handleSubmit} />
    </section>
  )
};

--文章详情页模块

文章详情页的state放在哪?面向feature拆分slice

const initialState = {
  data: null,
  status: 'idle',
  error: null,
};

export const fetchPost = createAsyncThunk('post/fetchPost', async (id) => {//获取文章详情
  const response = await client.get(`/fakeApi/posts/${id}`);
  return response.post;
});

export const addPostReaction = createAsyncThunk(
  'post/addPostReaction',
  async ({ id, reaction }) => {
    const response = await client.post(`/fakeApi/posts/${id}/reaction`, {
      reaction,
    });
    return response.post;
  },
);

const postSlice = createSlice({
  name: 'post',
  initialState,
  reducers: {
    clearPost(state, action) {
      return initialState;
    },
  },
  extraReducers: {
    [fetchPost.pending]: (state, action) => {
      state.status = 'loading';
    },
    [fetchPost.fulfilled]: (state, action) => {
      state.status = 'succeeded';
      state.data = action.payload;
    },
    [fetchPost.rejected]: (state, action) => {
      state.status = 'failed';
      state.error = action.error.message;
    },
    [addPostReaction.fulfilled]: (state, action) => {
      state.data = action.payload;
    },
  },
});

其他状态管理方案

RTK Query

Recoil

Context + Hooks