差異處
這裏顯示兩個版本的差異處。
web:reactjs:react_hook:usecontext [2019/02/25 00:06] tony [Introduction] |
web:reactjs:react_hook:usecontext [2023/06/25 09:48] |
||
---|---|---|---|
行 1: | 行 1: | ||
- | ====== React Hook - useContext ====== | ||
- | ===== Introduction ===== | ||
- | 我在Study React所撰寫的第一個範例是系統的登入與登出,而登入狀態需要由上層元件傳遞給登入與登出元件做修改。在這範例中,由於上層元件與登入元件只有一層,所以我可以透過建構元將狀態傳遞過去;然而登出元件與上層元件相差太多層,傳遞並不是那麼容易。\\ | ||
- | \\ | ||
- | 在React Hook推出之前,有flux、Redux,或者是後來推出的Context,都可以解決這個問題;而本篇要介紹的useContext,我認為比前面幾個都還容易達到需求。\\ | ||
- | \\ | ||
- | 以一個登入畫面為例,我們在App元件中,希望能透過authState來決定該顯示登入畫面又或者是登入後的畫面;假如使用者尚未登入,那我們會將會Route至登入畫面,即LoginForm元件。在LoginForm中,如果使用者成功登入後,會去修改這個authState;而React會因為這個狀態的改變,重新render App元件並Route至登入後的畫面。我將在App中createContext,而LoginForm將會useContext去做狀態的修改。 | ||
- | ===== createContext ===== | ||
- | <code javascript> | ||
- | export const AuthContext = React.createContext(null); | ||
- | |||
- | function App(){ | ||
- | const [ authState, setAuthState ] = useState(false); | ||
- | |||
- | let route_target; | ||
- | if( !authState) { | ||
- | route_target = <Route path="/" name="Login" component={LoginForm}/>; | ||
- | } else | ||
- | route_target = <Route path="/" name="Home" component={DefaultLayout} />; | ||
- | |||
- | return ( | ||
- | <AuthContext.Provider value={[authState, setAuthState]}> | ||
- | <HashRouter> | ||
- | <Switch> | ||
- | {route_target} | ||
- | </Switch> | ||
- | </HashRouter> | ||
- | </AuthContext.Provider> | ||
- | ); | ||
- | } | ||
- | </code> | ||
- | ===== useContext ===== | ||
- | <code javascript> | ||
- | function LoginForm(){ | ||
- | const [ authState, setAuthState ] = useContext(AuthContext); | ||
- | const [ username, setUserName ] = useState(""); | ||
- | const [ password, setPassword ] = useState(""); | ||
- | const [ feedback, setFeedBack ] = useState(null); | ||
- | |||
- | const validateForm = ()=>{ | ||
- | return username.length > 0 && password.length > 0; | ||
- | } | ||
- | |||
- | const handleSubmit = (e)=>{ | ||
- | e.preventDefault(); | ||
- | try { | ||
- | Auth.signIn(username, password); | ||
- | setAuthState(true); | ||
- | } catch( err ) { | ||
- | setFeedBack(err.message); | ||
- | } | ||
- | } | ||
- | |||
- | const showFeedback = ()=>{ | ||
- | return feedback != null; | ||
- | } | ||
- | |||
- | return ( | ||
- | | ||
- | <div className="Login"> | ||
- | <form onSubmit={handleSubmit}> | ||
- | <FormGroup> | ||
- | <Label>Username</Label> | ||
- | <Input | ||
- | autoFocus | ||
- | aria-label="username-input" | ||
- | type="text" | ||
- | value={username} | ||
- | onChange={e=>setUserName(e.target.value)} | ||
- | ></Input> | ||
- | </FormGroup> | ||
- | <FormGroup> | ||
- | <Label>Password</Label> | ||
- | <Input | ||
- | value={password} | ||
- | aria-label="password-input" | ||
- | onChange={e=>setPassword(e.target.value)} | ||
- | type="password" | ||
- | ></Input> | ||
- | </FormGroup> | ||
- | <Button | ||
- | block | ||
- | disabled={!validateForm()} | ||
- | type="submit" | ||
- | onClick={()=>{}} | ||
- | > | ||
- | Login | ||
- | </Button> | ||
- | <br/> | ||
- | <Alert color="danger" isOpen={showFeedback()} fade={true} aria-label="feedback">{feedback}</Alert> | ||
- | </form> | ||
- | | ||
- | </div> | ||
- | ); | ||
- | } | ||
- | </code> | ||
- | ===== Unit Test ===== | ||
- | <code java> | ||
- | import {render, fireEvent, cleanup} from 'react-testing-library'; | ||
- | import React, { useState, useEffect } from 'react'; | ||
- | import LoginForm from '../LoginForm/LoginForm'; | ||
- | import { AuthContext } from "../../../App"; | ||
- | import Auth from "../Components/Auth/Auth" | ||
- | |||
- | let authState = false; | ||
- | function LoginFormComp(){ | ||
- | const [ state, setState ] = useState(false); | ||
- | useEffect(()=>{ | ||
- | authState = state; | ||
- | }); | ||
- | return <AuthContext.Provider value={[state, setState]}><LoginForm/></AuthContext.Provider>; | ||
- | } | ||
- | |||
- | |||
- | describe('<LoginForm/>',()=>{ | ||
- | afterEach(cleanup); | ||
- | |||
- | const inputLoginInfo = (utils, username, password)=>{ | ||
- | const username_input = utils.getByLabelText('username-input'); | ||
- | const password_input = utils.getByLabelText('password-input'); | ||
- | |||
- | fireEvent.change(username_input, { | ||
- | target: { value: username } | ||
- | }); | ||
- | fireEvent.change(password_input, { | ||
- | target: { value: password } | ||
- | }); | ||
- | }; | ||
- | |||
- | const clickLoginBtn = (utils)=>{ | ||
- | const loginButton = utils.getByText('Login'); | ||
- | fireEvent.click(loginButton); | ||
- | }; | ||
- | |||
- | const givenAuthSignInSuccessfully = ()=>{ | ||
- | const auth_do_nothing = jest.fn(); | ||
- | auth_do_nothing.mockImplementation(()=>{}); | ||
- | Auth.signIn = auth_do_nothing.bind(Auth); | ||
- | }; | ||
- | |||
- | it('Login success', ()=>{ | ||
- | // given | ||
- | givenAuthSignInSuccessfully(); | ||
- | const formComp = render(<LoginFormComp/>); | ||
- | |||
- | // when | ||
- | inputLoginInfo(formComp, 'ADMIN', 'ADMIN'); | ||
- | clickLoginBtn(formComp); | ||
- | |||
- | // then | ||
- | expect(authState).toBeTruthy(); | ||
- | }); | ||
- | }); | ||
- | </code> | ||
- | ===== Reference ===== | ||
- | * [[https://tinkerylabs.com/react-context-api-with-hooks/|React Context API + State Hook]] | ||
- | * [[https://serverless-stack.com/chapters/who-is-this-guide-for.html|Learn to Build Full-Stack Apps with Serverless and React on AWS]] | ||
- | |||
- | ===== ===== | ||
- | ---- | ||
- | \\ | ||
- | ~~DISQUS~~ |