最近开始学习React,跟着Nolan老师开发Jira,有很多干货,这里把课程的第5章用户认证实践做一个总结,我把我的代码都放在codesandbox上了,修改了部分老师的代码
如果用户登录了,就显示AuthenticatedApp,为了方便,页面显示Hello World以及有个logout button
如果用户没有登录,就显示UnauthenticatedApp,有login以及register的页面
login框架大概如图,登录信息会存到localstorage中,然后通过context统一管理登录的信息
context属于全局变量,所以需要在
<AppProviders>
<App />
</AppProviders>
其中AppProviders中包含了AuthProvider
export const AppProviders = ({ children }: { children: ReactNode }) => {
return <AuthProvider>
{children}
</AuthProvider>;
};
AuthProvider组件就是提供Context的,给全局提供{ user, login, register, logout }
export const AuthProvider = ({ children }: { children: ReactNode }) => {
//实现{ user, login, register, logout }
...
return (
<AuthContext.Provider
children={children}
value={{ user, login, register, logout }}
/>
);
};
AuthProvider中使用到了React的context,其中用user, login, register, logout
const AuthContext = React.createContext<{
user: User | null;
login: (form: AuthForm) => Promise<void>;
register: (form: AuthForm) => Promise<void>;
logout: () => Promise<void>;
} | undefined>(undefined);
那么有了AuthProvider了,其它组件使用可以通过useContext调用
{ user, login, register, logout } =
React.useContext(AuthContext)
向外可以通过hook提供context
export const UseAuth = () => {
const context = React.useContext(AuthContext);
if (!context) throw new Error("UseAuth error");
return context;
};
这个页面就是用户可以选择登陆或者注册,点击可以切换页面
export const UnauthenticatedApp = () => {
const [isLogin, setIsLogin] = useState(false);
return (
<div>
{isLogin ? <LoginScreen /> : <RegisterScreen />}
<button onClick={()=>setIsLogin(!isLogin)} >switch</button>
</div>
);
};
登录页面是一个表单,实现比较简单,重点是使用UseAuth()
export const LoginScreen = () => {
const { user, login } = UseAuth();
if(user) {
console.log('user has login', user)
}
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
const username = (event.currentTarget.elements[0] as HTMLInputElement)
.value;
const password = (event.currentTarget.elements[1] as HTMLInputElement)
.value;
console.log("useranme: ", username, "password: ", password);
login({ username, password });
};
return (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="username">用户名</label>
<input type="text" id={"username"} />
</div>
<div>
<label htmlFor="password">密码</label>
<input type="password" id="password" />
</div>
<button type={"submit"}>登录</button>
</form>
);
};
注册页面跟登录页面非常像,就是使用UseAuth的register
export const RegisterScreen = () => {
const {register} = UseAuth()
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
const username = (event.currentTarget.elements[0] as HTMLInputElement)
.value;
const password = (event.currentTarget.elements[1] as HTMLInputElement)
.value;
console.log("useranme: ", username, "password: ", password);
register({username, password})
};
return (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="username">用户名</label>
<input type="text" id={"username"} />
</div>
<div>
<label htmlFor="password">密码</label>
<input type="password" id="password" />
</div>
<button type={"submit"}>注册</button>
</form>
);
};
这个页面就是显示hello world并且有个退出键
export const AuthenticatedApp = () => {
const { logout } = UseAuth();
return (
<div>
<h1>hello world</h1>
<button onClick={() => logout()}>logout</button>
</div>
);
};
通过axios发送http的请求,上面提到的AuthProvider中可以直接调用这里的方法就行
interface data {
username: string;
password: string;
}
export const login = async (data: data) => {
const loginUrl = `${apiUrl}/posts`;
return axios.post(loginUrl);
};
export const register = async (data: data) => {
const loginUrl = `${apiUrl}/posts`;
return axios.post(loginUrl);
};
export const logout = async () => {};
当用户登录或者注册,状态信息显示保存在localstorage的
const setUserToLocalstorage = (user: User) => {
window.localStorage.setItem(
localStorageUserKey,
JSON.stringify({ id: user.id, token: user.token })
);
};
const removeUserFromStorage = () => {
window.localStorage.removeItem(localStorageUserKey)
}
const checkLocalstorage = () => {
let storageUser = window.localStorage.getItem(localStorageUserKey);
if (storageUser) return JSON.parse(storageUser);
};
之前提到了AuthProvider会提供{ user, login, register, logout },没有具体讲到实现
这个组件需要在调用的时候先看看localstorage有没有信息,有的话先赋值user
在调用login时候把信息保存在localstorage上,logout把localstorage的信息移除
export const AuthProvider = ({ children }: { children: ReactNode }) => {
const [user, setUser] = useState<User | null>(null);
const login = (form: AuthForm) => {
Auth.login(form).then((res) => {
let loginUser = { id: res.data.id, token: res.data.id };
setUserToLocalstorage(loginUser);
setUser(loginUser);
});
};
const register = (form: AuthForm) =>
Auth.register(form).then((res) =>
setUser({ id: res.data.id, token: res.data.id })
);
const logout = () =>
Auth.logout().then(() => {
removeUserFromStorage()
setUser(null)
});
useEffect(() => {
let cacheUser = bootstrapUser();
setUser(cacheUser);
}, []);
return (
<AuthContext.Provider
children={children}
value={{ user, login, register, logout }}
/>
);
};
实现用户登录是很多App都会有的功能,这里通过简单的页面展示实现了一个不错的架构,通过Context管理用户信息,然后使用UseAuth调用,较少了耦合度